/* eslint-disable */
/**
 * @author zhangmeng、huzw、zenghx
 * @time 2019.04.10
 * @update 2020.05.11
 */
var H5Live = {
  version: '1.0.6',

  debug: false,

  language: 'zh_CN', // zh_CN | en

  // value of H5Live.Enum.BrowserType
  browserType: null,

  windowObjects: null,

  callObject: null,

  talkObject: null,

  commandCommunicateObject: null,

  gatherVideoStreamObject: null,

  dataNotifyObject: null,

  inited: false,

  cb: null,

  video: null,

  Plug: {

    GetHtml: function (options) {
      var options = options || {}

      var html = ''
      html += '<video muted id="' + options.domid + '_video" name="' + options.domname + '_video" style="width:0px;height:0px;object-fit:fill;"></video>'
      html += '<audio id="' + options.domid + '_audio" name="' + options.domname + '_audio" style="width:0px;height:0px;object-fit:fill;"></audio>'

      return html
    },

    end: true
  },

  // 是否已经初始化
  IsInited: function () {
    return H5Live.inited || false
  },

  // 全局判断是否支持H5VIDEO
  ValidObjectLoad: function () {
    try {
      var fn = 'H5Live.ValidObjectLoad'

      if (H5Live.IsInited()) {
        H5Live.Utils.Log(fn, 'H5Live has inited~')
        return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
      }

      // 检测H5VIDEO是否实现装载
      var _h5livebox = null
      var _boxDomId = '_h5live-xvideo-box'
      if (!document.getElementById(_boxDomId)) {
        _h5livebox = document.createElement('DIV')
        _h5livebox.setAttribute('id', _boxDomId)
        _h5livebox.setAttribute('attention', 'for h5live video verfifying...')
        // 必需双引号获取
        document.getElementsByTagName('body').item(0).appendChild(_h5livebox)
      } else {
        _h5livebox = document.getElementById(_boxDomId)
      }

      _h5livebox.style.display = 'none'
      _h5livebox.style.visibility = 'hidden'

      var html = []
      html.push(H5Live.Plug.GetHtml({
        domid: '_h5live-xvideo',
        domname: '_h5live-xvideo'
      }))

      _h5livebox.insertAdjacentHTML('beforeEnd', html.join(''))

      if (typeof document.getElementById('_h5live-xvideo_video').play === 'undefined') {
        H5Live.Utils.Log(fn, 'load h5video error~')
        return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_LOAD_H5VIDEO_OBJECT_FAILED)
      }
      if (typeof document.getElementById('_h5live-xvideo_audio').play === 'undefined') {
        H5Live.Utils.Log(fn, 'load h5audio error~')
        return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_LOAD_H5VIDEO_OBJECT_FAILED)
      }

      H5Live.inited = true

      return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
    }
  },

  CheckBrowserType: function () {
    try {
      var fn = 'H5Live.CheckBrowserType'

      var explorer = window.navigator.userAgent.toLowerCase()
      // ie
      if (explorer.indexOf('MSIE') >= 0) {
        var ver = explorer.match(/msie ([\d.]+)/)[1]
        if (parseInt(ver) < 9) {
          // alert('浏览器不支持HTML5')
        }
      }
      // firefox
      else if (explorer.indexOf('Firefox') >= 0) {
        var ver = explorer.match(/firefox\/([\d.]+)/)[1]
      }
      // Chrome
      else if (explorer.indexOf('Chrome') >= 0) {
        var ver = explorer.match(/chrome\/([\d.]+)/)[1]
      }
      // Opera
      else if (explorer.indexOf('Opera') >= 0) {
        var ver = explorer.match(/opera.([\d.]+)/)[1]
      }
      // Safari
      else if (explorer.indexOf('Safari') >= 0) {
        var ver = explorer.match(/version\/([\d.]+)/)[1]
      }
    } catch (e) {
    }
  },

  /**
   * 获取浏览器相关信息
   * @author zenghx
   * @date 2019-07-01
   * @return
   *
   */
  /*GetBrowseType: function () {
    // 权重：系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
    const ua = navigator.userAgent.toLowerCase()
    const testUa = regexp => regexp.test(ua)
    const testVs = regexp => (ua.match(regexp) + "").replace(/[^0-9|_.]/ig, "").replace(/_/ig, ".")

    // 系统
    let system = "unknown";
    if (testUa(/windows|win32|win64|wow32|wow64/ig)) {
      system = "windows" // window系统
    } else if (testUa(/macintosh|macintel/ig)) {
      system = "osx" // osx系统
    } else if (testUa(/x11/ig)) {
      system = "linux" // linux系统
    } else if (testUa(/android|adr/ig)) {
      system = "android" // android系统
    } else if (testUa(/ios|iphone|ipad|ipod|iwatch/ig)) {
      system = "ios" // ios系统
    }
    // 系统版本
    let systemVs = "unknown"
    if (system === "windows") {
      if (testUa(/windows nt 5.0|windows 2000/ig)) {
        systemVs = "2000"
      } else if (testUa(/windows nt 5.1|windows xp/ig)) {
        systemVs = "xp"
      } else if (testUa(/windows nt 5.2|windows 2003/ig)) {
        systemVs = "2003"
      } else if (testUa(/windows nt 6.0|windows vista/ig)) {
        systemVs = "vista"
      } else if (testUa(/windows nt 6.1|windows 7/ig)) {
        systemVs = "7"
      } else if (testUa(/windows nt 6.2|windows 8/ig)) {
        systemVs = "8"
      } else if (testUa(/windows nt 6.3|windows 8.1/ig)) {
        systemVs = "8.1"
      } else if (testUa(/windows nt 10.0|windows 10/ig)) {
        systemVs = "10"
      }
    } else if (system === "osx") {
      systemVs = testVs(/os x [\d._]+/ig)
    } else if (system === "android") {
      systemVs = testVs(/android [\d._]+/ig)
    } else if (system === "ios") {
      systemVs = testVs(/os [\d._]+/ig)
    }
    // 平台
    let platform = "unknow"
    if (system === "windows" || system === "osx" || system === "linux") {
      platform = "desktop" // 桌面端
    } else if (system === "android" || system === "ios" || testUa(/mobile/ig)) {
      platform = "mobile" // 移动端
    }
    // 内核和载体
    let engine = "unknow"
    let supporter = "unknow"
    if (testUa(/applewebkit/ig) && testUa(/safari/ig)) {
      engine = "webkit" // webkit内核
      if (testUa(/edge/ig)) {
        supporter = "edge" // edge浏览器
      } else if (testUa(/opr/ig)) {
        supporter = "opera" // opera浏览器
      } else if (testUa(/chrome/ig)) {
        supporter = "chrome" // chrome浏览器
      } else {
        supporter = "safari" // safari浏览器
      }
    } else if (testUa(/gecko/ig) && testUa(/firefox/ig)) {
      engine = "gecko" // gecko内核
      supporter = "firefox" // firefox浏览器
    } else if (testUa(/presto/ig)) {
      engine = "presto" // presto内核
      supporter = "opera" // opera浏览器
    } else if (testUa(/trident|compatible|msie/ig)) {
      engine = "trident" // trident内核
      supporter = "iexplore" // iexplore浏览器
    }
    // 内核版本
    let engineVs = "unknow"
    if (engine === "webkit") {
      engineVs = testVs(/applewebkit\/[\d.]+/ig)
    } else if (engine === "gecko") {
      engineVs = testVs(/gecko\/[\d.]+/ig)
    } else if (engine === "presto") {
      engineVs = testVs(/presto\/[\d.]+/ig)
    } else if (engine === "trident") {
      engineVs = testVs(/trident\/[\d.]+/ig)
    }
    // 载体版本
    let supporterVs = "unknow"
    if (supporter === "chrome") {
      supporterVs = testVs(/chrome\/[\d.]+/ig)
    } else if (supporter === "safari") {
      supporterVs = testVs(/version\/[\d.]+/ig)
    } else if (supporter === "firefox") {
      supporterVs = testVs(/firefox\/[\d.]+/ig)
    } else if (supporter === "opera") {
      supporterVs = testVs(/opr\/[\d.]+/ig)
    } else if (supporter === "iexplore") {
      supporterVs = testVs(/(msie [\d.]+)|(rv:[\d.]+)/ig)
    } else if (supporter === "edge") {
      supporterVs = testVs(/edge\/[\d.]+/ig)
    }
    // 外壳和外壳版本
    let shell = "none"
    let shellVs = "unknow"
    if (testUa(/micromessenger/ig)) {
      shell = "wechat" // 微信浏览器
      shellVs = testVs(/micromessenger\/[\d.]+/ig)
    } else if (testUa(/qqbrowser/ig)) {
      shell = "qq" // QQ浏览器
      shellVs = testVs(/qqbrowser\/[\d.]+/ig)
    } else if (testUa(/ubrowser/ig)) {
      shell = "uc" // UC浏览器
      shellVs = testVs(/ubrowser\/[\d.]+/ig)
    } else if (testUa(/2345explorer/ig)) {
      shell = "2345" // 2345浏览器
      shellVs = testVs(/2345explorer\/[\d.]+/ig)
    } else if (testUa(/metasr/ig)) {
      shell = "sougou" // 搜狗浏览器
    } else if (testUa(/lbbrowser/ig)) {
      shell = "liebao" // 猎豹浏览器
    } else if (testUa(/maxthon/ig)) {
      shell = "maxthon" // 遨游浏览器
      shellVs = testVs(/maxthon\/[\d.]+/ig)
    } else if (testUa(/bidubrowser/ig)) {
      shell = "baidu" // 百度浏览器
      shellVs = testVs(/bidubrowser [\d.]+/ig)
    }
    return Object.assign({
        engine, // webkit gecko presto trident
        engineVs,
        platform, // desktop mobile
        supporter, // chrome safari firefox opera iexplore edge
        supporterVs,
        system, // windows osx linux android ios
        systemVs
    }, shell === "none" ? {} : {
        shell, // wechat qq uc 2345 sougou liebao maxthon baidu
        shellVs
    })
  },*/

  /**
     * @desc 全局销毁接口（退出页面时调用）
     * @returns
     */
  GlobalDestory: function () {
    try {
      var fn = 'H5Live.GlobalDestory'

      this.DestoryWindows()
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
    }
  },

  /**
     * @desc 销毁窗口对象
     * @params
     * 	- winkey(string) 窗口容器DOM ID，可选
     */
  DestoryWindows: function (winkey) {
    try {
      var fn = 'H5Live.DestoryWindows'

      var SELF = this

      if (this.windowObjects instanceof H5Live.Utils.Hash) {
        this.windowObjects.each(function (item, index) {
          if (item.key === winkey || (!winkey && item.key)) {
            var winObj = item.value

            if (winObj) {
              winObj.CloseVideo()
              winObj.CloseAudio()
            }

            SELF.windowObjects.remove(item.key)
          }
        })
      }
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
    }
  },

  /**
     * @desc 窗口实例化对象类
     * @params
     * 	- containerOrId * 窗口容器DOM对象或ID object of [context.]document.getElementById(domid) | domid
     */
  Window: function (containerOrId, customParams) {
    try {
      var fn = 'H5Live.Window'

      this.video = {
        puid: null,
        idx: null,
        type: 'IV',
        ws: null,
        containerId: null,
        isPlaying: false,
        _cb: null
      }

      this.video_IA = {
        puid: null,
        idx: null,
        type: 'IV',
        ws: null,
        containerId: null,
        isPlaying: false,
        _cb: null
      }

      this.audio = {
        puid: null,
        idx: null,
        type: 'IV',
        containerId: null,
        isPlaying: false,
        _cb: null
      }

      this.vod = {
        ws: null, // Websocket实例对象
        isPlaying: false, // 是否在播放
        type: 'platform', // platform(default) 平台录像 | device 设备前端录像
        puid: '', // type = platform 平台存储器设备，type = device 前端设备
        diskLetter: '', // 盘符，前端设备点播需要，平台点播该参数忽略
        idx: 0, // 摄像头索引，前端设备点播需要，平台点播该参数忽略
        fileName: '', // 点播文件路径名称
        beginTime: '', // 文件起始时间（录像查询时返回）
        endTime: '', // 文件结束时间（录像查询时返回）
        offsetTime: '', // 点播文件偏移时长，单位为秒
        speed: 1, // 文件播放速度1(default)、2、4、8；1/2、1/4、1/8
        _cb: null // 回调，保留
      }

      this.record = {
        ws: null,
        puid: null,
        idx: null,
        type: 'IV',
        containerId: null,
        isRecording: false,
        _cb: null
      }

      this.videoMSE = new MediaSource()
      this.audioMSE = new MediaSource()

      this.videoSegments = { video: [] }
      this.audioSegments = { audio: [] }
      this.videosb = { video: null }
      this.audiosb = { audio: null }
      this.metadata = {
        audioCodec: 'mp4a.40.5',
        videoCodec: 'avc1.42E01E'
      }
      var _SELF = this
      _SELF.audioMSE.addEventListener('sourceopen', function (e) {
        _SELF.AddSourceBuffer('audio')
      })
      _SELF.videoMSE.addEventListener('sourceopen', function (e) {
        _SELF.AddSourceBuffer('video')
      })

      var container = null

      if (typeof containerOrId === 'object' && typeof containerOrId.id !== 'undefined') {
        container = containerOrId
      } else {
        if (containerOrId == null || typeof containerOrId === 'undefined' || document.getElementById(containerOrId) == null) {
          H5Live.Utils.Log(fn, 'containerOrId error~')
          return false
        }

        container = document.getElementById(containerOrId)
      }
      // 判断全局中是否已经存在
      if (H5Live.windowObjects === null) {
        H5Live.windowObjects = new H5Live.Utils.Hash()
      }
      if (H5Live.windowObjects.containsKey(container.id)) {
        H5Live.Utils.Log(fn, 'The same containerOrId has existed error~')
        return false
      }

      var t_wndname = container.id
      var t_wndid = t_wndname
      container.innerHTML = H5Live.Plug.GetHtml({
        pname: 'wnd',
        domid: t_wndid,
        domname: t_wndname
      })

      this.video_object = container.firstChild || document.getElementById(t_wndid + '_video')
      this.video_object.setAttribute('x-webkit-airplay', 'allow')
      this.video_object.setAttribute('playsinline', '')
      this.video_object.setAttribute('webkit-playsinline', '')
      this.video_object.setAttribute('x5-playsinline', '')
      this.video_object.setAttribute('x5-video-player-type', 'h5')
      this.video_object.setAttribute('x5-video-player-fullscreen', true)
      this.video_object.preload = 'none'

      this.audio_object = container.lastChild || document.getElementById(t_wndid + '_audio')
      this.audio_object.preload = 'none'

      // 设置视频标签自适应模式 fill | contain | cover | none | scale-down
      this.set_object_fit = function (_object_fit) {
        try {
          var _object_fit = _object_fit || 'fill'
          if (H5Live.Utils.Array.indexOf(['fill', 'contain', 'cover', 'none', 'scale-down'], _object_fit) == -1) {
            return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_FAILED)
          }
          if (this.video_object) {
            this.video_object.style['object-fit'] = _object_fit
          }

          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
        } catch (e) {
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }
      if (customParams && typeof customParams === 'object') {
        this.set_object_fit(customParams.object_fit || 'fill')
      }

      if (!container.firstChild && container.firstChild.id != t_wndid && !document.getElementById(t_wndid)) {
        H5Live.Utils.Log(fn, 'include wnd plugin error~')
        return false
      }

      this.audio_object.src = URL.createObjectURL(this.audioMSE)
      this.audio_object.load()

      this.video_object.src = URL.createObjectURL(this.videoMSE)
      this.video_object.load()

      // 窗口容器对象
      this.container = container || null
      // 窗口标识键
      this.key = this.container.id

      // 全局事件回调通知函数
      this.event_callback = customParams.callback || function () { }

      // 打开一路视频
      this.OpenVideo = function (options) {
        try {
          var fn = 'WND#OpenVideo'
          options = options || {}

          // 标记窗口是否在尝试视频流断线重连中，有些状态将暂停通知
          if (options.sbr_running) {
            _SELF.sbr_running = options.sbr_running
          }

          if (_SELF.video.isPlaying) {
            _SELF.CloseVideo()
          }
          if (_SELF.vod.isPlaying) {
            _SELF.CloseVod()
          }

          // @huzw 2020.08.21 add
          // 为了规避因上层快速连续调用多次函数，出现建立多个WS实例对象情况，从而导致视频画面显示异常Bug
          if (_SELF.video.ws != null) {
            // 判断是不是相同的参数，不同的参数，就停止连接，创建新的
            let _preventFlag = false

            if (_SELF.video != null) {
              if (_SELF.video.puid == options.puid && _SELF.video.idx == options.idx) {
                _preventFlag = true
              }
            }

            // 判断WS对象状态，如果不正确，就关闭
            if (_SELF.video.ws.readyState === 3) {
              _preventFlag = false
            }

            if (_preventFlag) {
              // 同路视频，阻止重新播放
              return false
            } else {
              // 关闭本次视频
              _SELF.CloseVideo(true)
            }
          }

          _SELF.video.containerId = _SELF.audio.containerId = options.containerId
          _SELF.video.puid = _SELF.audio.puid = options.puid
          _SELF.video.type = _SELF.audio.type = 'IV'
          _SELF.video.idx = _SELF.audio.idx = options.idx
          _SELF.video.streamType = _SELF.audio.streamType = options.streamType || 'REALTIME'

          _SELF.video.ws = new WebSocket(options.wsPath)
          _SELF.video.ws.binaryType = 'arraybuffer'
          _SELF.video.ws.onopen = function (e) {
            console.log('连接服务器成功')

            if (!_SELF.sbr_running) {
              _SELF.event_callback.call(_SELF, {
                action: 'ws.open',
                type: 'video',
                instance: _SELF,
                data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
              })
            }

            var request_xml = '<DstRes Type="IV" Idx="' + options.idx + '" PUID="' + options.puid + '" OptID="CTL_COMMONRES_StartStream_PushMode">' +
              '   <Param StreamType="' + options.streamType + '" Mixed="1" DstType="' + options.dstType + '"></Param>' +
              '</DstRes>'

            var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
            _SELF.video.ws.send(send_buffer)
            var promiseVideo = _SELF.video_object.play()
            if (promiseVideo) {
              promiseVideo['catch'](function (e) { })
            }

            _SELF.AddSourceBuffer('audio')
            _SELF.AddSourceBuffer('video')
          }
          _SELF.video.ws.onclose = function (e) {
            console.log('video websocket 连接关闭')
            console.log(e)

            if (_SELF.sbr_running) {
              // return false
            }

            _SELF.event_callback.call(_SELF, {
              action: 'ws.close',
              type: 'video',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
          }
          _SELF.video.ws.onerror = function (e) {
            console.log('video websocket 连接出错')
            console.log(e)

            if (_SELF.sbr_running) {
              // return false
            }

            _SELF.event_callback.call(_SELF, {
              action: 'ws.error',
              type: 'video',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
          }
          _SELF.video.ws.onmessage = function (e) {
            try {
              // if (_SELF.video.ws.readyState != 1) {
              //    return false;
              // }
              var data = new Uint8Array(e.data)
              var pos = 0
              var type = data[pos++]
              switch (type) {
                case 0x08:
                case 0x09:
                  var segtype = type == 0x08 ? 'audio' : 'video'
                  var seg = new Uint8Array(e.data, pos)

                  // pos += 3; // skip 3 bytes of command
                  pos += 4 // skip 4 bytes of box size

                  var evttype = 'playeaseMp4Segment'
                  if (data[pos] === 0x66 && data[pos + 1] === 0x74 && data[pos + 2] === 0x79 && data[pos + 3] === 0x70) { // is ftyp box
                    evttype = 'playeaseMp4InitSegment'
                  }

                  var data = {
                    data: seg,
                    tp: segtype,
                    type: evttype
                  }
                  if (data.tp == 'audio') {
                    if (_SELF.audio.isPlaying && evttype == 'playeaseMp4Segment') {
                      data.data.info = data.info
                      _SELF.audioSegments[data.tp].push(data.data)

                      if (_SELF.audioSegments[data.tp].length == 0) {
                        return
                      }

                      var sb = _SELF.audiosb[data.tp]
                      if (!sb || sb.updating) {
                        return
                      }
                      var seg = _SELF.audioSegments[data.tp].shift()
                      try {
                        sb.appendBuffer(seg)
                      } catch (err) {
                      }
                    } else if (evttype == 'playeaseMp4InitSegment') {
                      _SELF.audioSegments[data.tp].push(data.data)
                    }
                  } else {
                    if (_SELF.sbr_running) {
                      // 补充推送连接已建立通知
                      _SELF.event_callback.call(_SELF, {
                        action: 'ws.open',
                        type: 'video',
                        instance: _SELF,
                        data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
                      })
                    }

                    if (evttype == 'playeaseMp4Segment') {
                      data.data.info = data.info
                      _SELF.videoSegments[data.tp].push(data.data)

                      if (_SELF.videoSegments[data.tp].length == 0) {
                        return
                      }
                      var sb = _SELF.videosb[data.tp]

                      if (!sb || sb.updating) {
                        return
                      }

                      var seg = _SELF.videoSegments[data.tp].shift()
                      try {
                        sb.appendBuffer(seg)
                        if (!_SELF.video.isPlaying) {

                          _SELF.sbr_running = false

                          _SELF.video.isPlaying = true

                          _SELF.event_callback.call(_SELF, {
                            action: 'stream.start',
                            type: 'video',
                            instance: _SELF,
                            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
                          })
                        }
                      } catch (err) {
                      }
                    } else if (evttype == 'playeaseMp4InitSegment') {
                      _SELF.videoSegments[data.tp].push(data.data)
                    }
                  }
                  break
                default:
                  H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
                  break
              }
            } catch (e) {
              H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
            }
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
        }
      }
      // 关闭视频
      this.CloseVideo = function (forceResetAll) {
        try {
          var fn = 'WND#CloseVideo'

          if (forceResetAll != false) {
            forceResetAll = true
          }

          if (_SELF.video_object != null) {
            if (_SELF.record.isRecording) {
              _SELF.CloseRecord()
            }
            if (_SELF.audio.isPlaying) {
              _SELF.CloseAudio()
            }

            if (_SELF.video.ws) {
              _SELF.video.ws.close()
              _SELF.video.ws = null
            }

            if (_SELF.video.isPlaying || forceResetAll)
            {
              if (_SELF.videoMSE && _SELF.videoMSE.readyState == 'open') {
                var sbLength = _SELF.videoMSE.sourceBuffers.length
                for (var i = 0; i < sbLength; i++) {
                  _SELF.videoMSE.removeSourceBuffer(_SELF.videoMSE.sourceBuffers[0])
                }

                _SELF.videoMSE.endOfStream()
              }

              _SELF.videoSegments = { video: [] }

              _SELF.video_object.pause()
              if (_SELF.videoMSE) {
                _SELF.video_object.src = URL.createObjectURL(_SELF.videoMSE)
              }
              _SELF.video_object.load()

              if (!_SELF.sbr_running) {
                _SELF.event_callback.call(_SELF, {
                  action: 'stream.stop',
                  type: 'video',
                  instance: _SELF,
                  data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
                })
              }

              _SELF.video.isPlaying = false
            }

            _SELF.videosb = { video: null }

          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      // 打开一路智能流视频
      this.OpenVideo_IA = function (options) {
        try {
          var fn = 'WND#OpenVideo_IA'
          options = options || {}
          if (_SELF.video.isPlaying) {
            _SELF.CloseVideo()
          }
          if (_SELF.video_IA.isPlaying) {
            _SELF.CloseVideo_IA()
          }
          if (_SELF.vod.isPlaying) {
            _SELF.CloseVod()
          }
          if (!_SELF.video_IA.isPlaying) {
            _SELF.video_IA.isPlaying = true
          }
          _SELF.video_IA.containerId = options.containerId
          _SELF.video_IA.puid = options.puid
          _SELF.video_IA.type = 'IV'
          _SELF.video_IA.idx = options.idx
          _SELF.video_IA.streamType = options.streamType || 'REALTIME'
          _SELF.video_IA.ws = new WebSocket(options.wsPath)
          _SELF.video_IA.ws.binaryType = 'arraybuffer'
          _SELF.video_IA.ws.onopen = function (e) {
            console.log('连接服务器成功')
            _SELF.event_callback.call(_SELF, {
              action: 'ws.open',
              type: 'video_IA',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
            var request_xml = '<DstRes Type="IV" Idx="0" PUID="' + _SELF.video_IA.puid + '" OptID="CTL_StartIA_Schedule">' +
              '   <Param StreamType="' + options.streamType + '" Mixed="1">' +
              '     <Alg>IA_FaceAndPlateDetect_XX</Alg>' +
              '     <APP APPIndex="" APPName="" AlgServerIndex=""></APP>' +
              '     <Src Type="1" DataSrc="0" PUID="' + _SELF.video_IA.puid + '" ResType="IV" ResIdx="' + _SELF.video_IA.idx + '" StreamType="' + options.streamType + '" UserToken="' + options.sessionID + '" />' +
              '   </Param>' +
              '</DstRes>'
            var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
            _SELF.video_IA.ws.send(send_buffer)

          }
          _SELF.video_IA.ws.onclose = function (e) {
            console.log('video_IA websocket 连接关闭')
            console.log(e)

            _SELF.event_callback.call(_SELF, {
              action: 'ws.close',
              type: 'video_IA',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
          }
          _SELF.video_IA.ws.onerror = function (e) {
            console.log('video_IA websocket 连接出错')
            console.log(e)

            _SELF.event_callback.call(_SELF, {
              action: 'ws.error',
              type: 'video_IA',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
          }
          _SELF.video_IA.ws.onmessage = function (e) {
            try {
              var skipByte = 0
              var coordinate_x = 0
              var coordinate_y = 0
              var coordinate_w = 0
              var coordinate_h = 0
              var buf = null
              var blob = null
              var Pic = null
              var picReader = null
              var resposeData = {}
              var SubType = H5Live.Utils.ArrayBufferDataView(e.data.slice(37, 41), 0, 1)
              if (SubType === 7) {
                var FaceNum = H5Live.Utils.ArrayBufferDataView(e.data.slice(53, 57), 0, 1)
                skipByte = 65 + 92 * FaceNum
                coordinate_x = H5Live.Utils.ArrayBufferDataView(e.data.slice(61, 65), 0, 1)
                coordinate_y = H5Live.Utils.ArrayBufferDataView(e.data.slice(65, 69), 0, 1)
                coordinate_w = H5Live.Utils.ArrayBufferDataView(e.data.slice(69, 73), 0, 1)
                coordinate_h = H5Live.Utils.ArrayBufferDataView(e.data.slice(73, 77), 0, 1)

                buf = new DataView(e.data.slice(skipByte));
                blob = new Blob([buf])

                Pic = null
                picReader  = new FileReader()
                picReader.addEventListener("load", function () {
                  Pic = picReader.result
                  resposeData = {
                    SubType: SubType,
                    coordinate_x: coordinate_x,
                    coordinate_y: coordinate_y,
                    coordinate_w: coordinate_w,
                    coordinate_h: coordinate_h,
                    Pic: Pic
                  }
                  _SELF.event_callback.call(_SELF, {
                    action: 'stream.response',
                    type: 'video_IA',
                    instance: _SELF,
                    data: resposeData
                  })
                }, false)
                picReader.readAsDataURL(blob)
              } else if (SubType === 6) {
                var PlateNum = H5Live.Utils.ArrayBufferDataView(e.data.slice(53, 57), 0, 1)
                skipByte = 65 + 96 * PlateNum
                coordinate_x = H5Live.Utils.ArrayBufferDataView(e.data.slice(61, 65), 0, 1)
                coordinate_y = H5Live.Utils.ArrayBufferDataView(e.data.slice(65, 69), 0, 1)
                coordinate_w = H5Live.Utils.ArrayBufferDataView(e.data.slice(69, 73), 0, 1)
                coordinate_h = H5Live.Utils.ArrayBufferDataView(e.data.slice(73, 77), 0, 1)

                buf = new DataView(e.data.slice(skipByte));
                blob = new Blob([buf])

                Pic = null
                picReader  = new FileReader()
                picReader.addEventListener("load", function () {
                  Pic = picReader.result
                  resposeData = {
                    SubType: SubType,
                    coordinate_x: coordinate_x,
                    coordinate_y: coordinate_y,
                    coordinate_w: coordinate_w,
                    coordinate_h: coordinate_h,
                    Pic: Pic
                  }
                  _SELF.event_callback.call(_SELF, {
                    action: 'stream.response',
                    type: 'video_IA',
                    instance: _SELF,
                    data: resposeData
                  })
                }, false)
                picReader.readAsDataURL(blob)
              } else if (SubType === 5) {
                var FaceNum = H5Live.Utils.ArrayBufferDataView(e.data.slice(53, 57), 0, 1)
                skipByte = 69 + 476 * FaceNum
                var score = H5Live.Utils.ArrayBufferDataViewFloat(e.data.slice(57, 61), 0, 1)
                coordinate_x = H5Live.Utils.ArrayBufferDataView(e.data.slice(61, 65), 0, 1)
                coordinate_y = H5Live.Utils.ArrayBufferDataView(e.data.slice(65, 69), 0, 1)
                coordinate_w = H5Live.Utils.ArrayBufferDataView(e.data.slice(69, 73), 0, 1)
                coordinate_h = H5Live.Utils.ArrayBufferDataView(e.data.slice(73, 77), 0, 1)

                /*var xmlResponse = Buffer.from(e.data.slice(81, 113)).toString('utf8')
                console.log(xmlResponse)*/
                var sexBuff = new Uint8Array(e.data.slice(81, 113))
                var nameBuff = new Uint8Array(e.data.slice(213, 277))
                var IDBuff = new Uint8Array(e.data.slice(181, 213))
                var imgBuff = new Uint8Array(e.data.slice(277, 325))
                var sexByte = 0
                for (var i=0;i<32;i++) {
                  if(parseInt(sexBuff[i]) === 0) {
                    sexByte = i
                    break
                  }
                }
                var nameByte = 0
                for (var i=0;i<64;i++) {
                  if(parseInt(nameBuff[i]) === 0) {
                    nameByte = i
                    break
                  }
                }
                var IDByte = 0
                for (var i=0;i<32;i++) {
                  if(parseInt(IDBuff[i]) === 0) {
                    IDByte = i
                    break
                  }
                }
                var imgByte = 0
                for (var i=0;i<48;i++) {
                  if(parseInt(imgBuff[i]) === 0) {
                    imgByte = i
                    break
                  }
                }

                var sexBuf = new DataView(e.data.slice(81, 81 + sexByte));
                var sexBlob = new Blob([sexBuf])
                var sex = ''
                var sexReader  = new FileReader()
                sexReader.addEventListener("load", function () {
                  sex = sexReader.result

                  var IDNumberBuf = new DataView(e.data.slice(181, 181 + IDByte));
                  var IDNumberBlob = new Blob([IDNumberBuf])
                  var IDNumber = ''
                  var IDNumberReader  = new FileReader()
                  IDNumberReader.addEventListener("load", function () {
                    IDNumber = IDNumberReader.result

                    var nameBuf = new DataView(e.data.slice(213, 213 + nameByte))
                    var nameBlob = new Blob([nameBuf])
                    var name = ''
                    var nameReader  = new FileReader()
                    nameReader.addEventListener("load", function () {
                      name = nameReader.result

                      var imageIDBuf = new DataView(e.data.slice(277, 277 + imgByte));
                      var imageIDBlob = new Blob([imageIDBuf])
                      var imageID = ''
                      var imageIDReader  = new FileReader()
                      imageIDReader.addEventListener("load", function () {
                        imageID = imageIDReader.result

                        var Time = H5Live.Utils.DateFormat('HH:mm:ss', new Date())

                        var PicBuf = new DataView(e.data.slice(skipByte));
                        var PicBlob = new Blob([PicBuf])
                        Pic = null
                        picReader  = new FileReader()
                        picReader.addEventListener("load", function () {
                          Pic = picReader.result
                          resposeData = {
                            SubType: SubType,
                            FaceScore: score,
                            coordinate_x: coordinate_x,
                            coordinate_y: coordinate_y,
                            coordinate_w: coordinate_w,
                            coordinate_h: coordinate_h,
                            Sex: sex,
                            IDNumber: IDNumber,
                            UserName: name,
                            imageID: imageID,
                            Pic: Pic,
                            Time: Time
                          }
                          _SELF.event_callback.call(_SELF, {
                            action: 'stream.response',
                            type: 'video_IA',
                            instance: _SELF,
                            data: resposeData
                          })
                        }, false)
                        picReader.readAsDataURL(PicBlob)
                      }, false)
                      imageIDReader.readAsText(imageIDBlob,'UTF-8')
                    }, false)
                    nameReader.readAsText(nameBlob,'GBK')
                  }, false)
                  IDNumberReader.readAsText(IDNumberBlob,'UTF-8')
                }, false)
                sexReader.readAsText(sexBlob,'GBK')

                /* var IDNumberBuf = new DataView(e.data.slice(171, 203));
                var IDNumberBlob = new Blob([IDNumberBuf])
                var IDNumber = ''
                var IDNumberReader  = new FileReader()
                IDNumberReader.addEventListener("load", function () {
                  IDNumber = IDNumberReader.result
                }, false)
                IDNumberReader.readAsText(IDNumberBlob)

                var nameBuf = new DataView(e.data.slice(203, 267));
                var nameBlob = new Blob([nameBuf])
                var name = ''
                var nameReader  = new FileReader()
                nameReader.addEventListener("load", function () {
                  name = nameReader.result
                }, false)
                nameReader.readAsText(nameBlob)

                var imageIDBuf = new DataView(e.data.slice(267, 315));
                var imageIDBlob = new Blob([imageIDBuf])
                var imageID = ''
                var imageIDReader  = new FileReader()
                imageIDReader.addEventListener("load", function () {
                  imageID = imageIDReader.result
                }, false)
                imageIDReader.readAsText(imageIDBlob) */
              }
            } catch (e) {
              H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
            }
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
        }
      }
      // 关闭智能流视频
      this.CloseVideo_IA = function () {
        try {
          var fn = 'WND#CloseVideo'
          if (_SELF.video_object != null) {
            if (_SELF.video_IA.isPlaying) {
              _SELF.video_IA.ws.close()
              _SELF.video_IA.ws = null
              _SELF.video_IA.isPlaying = false

              _SELF.event_callback.call(_SELF, {
                action: 'stream.stop',
                type: 'video_IA',
                instance: _SELF,
                data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
              })
            }
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      this.OpenAudio = function () {
        try {
          var fn = 'WND#OpenAudio'
          if (!_SELF.video.isPlaying) {
            return
          }
          if (_SELF.audio.isPlaying) {
            _SELF.CloseAudio()
          }

          _SELF.audio.isPlaying = true
          //_SELF.audiosb = {audio: null};
          //_SELF.audioSegments = { audio: [] }
          //_SELF.audioMSE = new MediaSource()
          var promiseAudio = _SELF.audio_object.play()
          if (promiseAudio) {
            promiseAudio['catch'](function (e) { })
          }

          _SELF.event_callback.call(_SELF, {
            action: 'stream.start',
            type: 'audio',
            instance: _SELF,
            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
          })
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      this.CloseAudio = function () {
        try {
          var fn = 'WND#CloseAudio'

          if (_SELF.audio_object != null) {
            if (_SELF.audio.isPlaying) {
              if (_SELF.audioMSE && _SELF.audioMSE.readyState == 'open') {
                var sbLength = _SELF.audioMSE.sourceBuffers.length
                for (var i = 0; i < sbLength; i++) {
                  _SELF.audioMSE.removeSourceBuffer(_SELF.audioMSE.sourceBuffers[0])
                }

                _SELF.audioMSE.endOfStream()
              }

              _SELF.audioSegments = { audio: [] }

              _SELF.audio_object.pause()
              if (_SELF.audioMSE) {
                _SELF.audio_object.src = URL.createObjectURL(_SELF.audioMSE)
              }
              _SELF.audio_object.load()

              // _SELF.audio_object.attr("muted",true);

              _SELF.event_callback.call(_SELF, {
                action: 'stream.stop',
                type: 'audio',
                instance: _SELF,
                data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
              })

              _SELF.audio.isPlaying = false
            }

            _SELF.audiosb = { audio: null }

          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      // 开启录像点播
      this.OpenVod = function (options) {
        try {
          var fn = 'WND#OpenVod'

          options = options || {}

          if (_SELF.video.isPlaying) {
            _SELF.CloseVideo()
          }
          if (_SELF.vod.isPlaying) {
            _SELF.CloseVod()
          }

          var _vod = _SELF.vod
          _vod.type = options.type || 'platform'
          if (typeof options.diskLetter !== 'undefined' && typeof options.idx !== 'undefined') {
            _vod.type = 'device'
          }
          _vod.puid = options.puid
          _vod.beginTime = options.beginTime
          _vod.endTime = options.endTime
          _vod.speed = options.speed || 1
          if (_vod.type == 'platform') {
            _vod.puid = _vod.puid || _vod.csuPuid || ''
            _vod.offsetTime = options.offsetTime || 0
            _vod.fileName = options.fileName
          } else {
            _vod.diskLetter = options.diskLetter
            _vod.idx = options.idx || 0
          }

          _vod.ws = new WebSocket(options.wsPath)
          _vod.ws.binaryType = 'arraybuffer'

          _vod.ws.onopen = function (e) {
            console.log('连接服务器成功')
            _SELF.event_callback.call(_SELF, {
              action: 'ws.open',
              type: 'vod',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })

            var request_xml = ''
            if (_vod.type == 'platform') {
              request_xml = H5Live.Utils.tpl(
                '<DstRes Type="SC" Idx="0" PUID="{#csuPuid}" OptID="CTL_FileVod_PushMode">' +
                '   <Param DiskLetter="" FileSystem="FAT32" IVIdx="" FileName="{#fileName}" BeginTime="{#beginTime}" EndTime="{#endTime}" Speed="{#speed}" OffsetTime="{#offsetTime}" ></Param>' +
                '</DstRes>', {
                  csuPuid: _vod.puid,
                  fileName: _vod.fileName,
                  beginTime: _vod.beginTime,
                  endTime: _vod.endTime,
                  speed: _vod.speed,
                  offsetTime: _vod.offsetTime
                }
              )
            } else {
              request_xml = H5Live.Utils.tpl(
                '<DstRes Type="SC" Idx="0" PUID="{#puid}" OptID="CTL_FileVod_PushMode">' +
                '   <Param DiskLetter="{#diskLetter}" FileSystem="" IVIdx="" FileName="" BeginTime="{#beginTime}" EndTime="{#endTime}" Speed="{#speed}" OffsetTime="{#offsetTime}" ></Param>' +
                '</DstRes>', {
                  puid: _vod.puid,
                  diskLetter: _vod.diskLetter,
                  idx: _vod.idx,
                  beginTime: _vod.beginTime,
                  endTime: _vod.endTime,
                  speed: _vod.speed
                }
              )
            }

            var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
            _vod.ws.send(send_buffer)

            var promiseVideo = _SELF.video_object.play()
            if (promiseVideo) {
              promiseVideo['catch'](function (e) { })
            }

            _SELF.AddSourceBuffer('audio')
            _SELF.AddSourceBuffer('video')
          }
          _vod.ws.onclose = function (e) {
            console.log('vod websocket 连接关闭')
            console.log(e)
            _SELF.event_callback.call(_SELF, {
              action: 'ws.close',
              type: 'vod',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
          }
          _vod.ws.onerror = function (e) {
            console.log('vod websocket 连接出错')
            console.log(e)
            _SELF.event_callback.call(_SELF, {
              action: 'ws.error',
              type: 'vod',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
          }
          _vod.ws.onmessage = function (e) {
            try {
              var data = new Uint8Array(e.data)
              var pos = 0
              var type = data[pos++]

              switch (type) {
                case 0x08:
                case 0x09:
                  var segtype = type == 0x08 ? 'audio' : 'video'
                  var seg = new Uint8Array(e.data, pos)

                  // pos += 3; // skip 3 bytes of command
                  pos += 4 // skip 4 bytes of box size

                  var evttype = 'playeaseMp4Segment'
                  if (data[pos] === 0x66 && data[pos + 1] === 0x74 && data[pos + 2] === 0x79 && data[pos + 3] === 0x70) { // is ftyp box
                    evttype = 'playeaseMp4InitSegment'
                  }

                  var data = {
                    data: seg,
                    tp: segtype,
                    type: evttype
                  }

                  if (data.tp == 'audio') {
                    if (evttype == 'playeaseMp4Segment') {
                      data.data.info = data.info
                      _SELF.audioSegments[data.tp].push(data.data)

                      if (_SELF.audioSegments[data.tp].length == 0) {
                        return
                      }

                      var sb = _SELF.audiosb[data.tp]
                      if (!sb || sb.updating) {
                        return
                      }
                      var seg = _SELF.audioSegments[data.tp].shift()
                      try {
                        sb.appendBuffer(seg)
                      } catch (err) {
                      }
                    } else if (evttype == 'playeaseMp4InitSegment') {
                      _SELF.audioSegments[data.tp].push(data.data)
                    }
                  } else {
                    if (evttype == 'playeaseMp4Segment') {
                      data.data.info = data.info
                      _SELF.videoSegments[data.tp].push(data.data)

                      if (_SELF.videoSegments[data.tp].length == 0) {
                        return
                      }
                      var sb = _SELF.videosb[data.tp]

                      if (!sb || sb.updating) {
                        return
                      }

                      var seg = _SELF.videoSegments[data.tp].shift()
                      try {
                        sb.appendBuffer(seg)
                        if (_SELF.vod.isPlaying != true) {
                          _SELF.vod.isPlaying = true

                          _SELF.event_callback.call(_SELF, {
                            action: 'stream.start',
                            type: 'vod',
                            instance: _SELF,
                            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
                          })
                        }
                      } catch (err) {
                      }
                    } else if (evttype == 'playeaseMp4InitSegment') {
                      _SELF.videoSegments[data.tp].push(data.data)
                    }
                  }
                  break
                default:
                  H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
                  break
              }
            } catch (e) {
              H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
            }
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excep error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }
      // 关闭录像点播
      this.CloseVod = function () {
        try {
          var fn = 'WND#CloseVod'
          if (_SELF.video_object != null && _SELF.audio_object != null) {
            var _vod = _SELF.vod

            if (_vod.ws != null) {
              _vod.ws.close()
              _vod.ws = null
            }

            if (_vod.isPlaying) {
              if (_SELF.audioMSE && _SELF.audioMSE.readyState == 'open') {
                var sbLength = _SELF.audioMSE.sourceBuffers.length
                for (var i = 0; i < sbLength; i++) {
                  _SELF.audioMSE.removeSourceBuffer(_SELF.audioMSE.sourceBuffers[0])
                }

                _SELF.audioMSE.endOfStream()
              }

              _SELF.audiosb = { audio: null }

              if (_SELF.videoMSE && _SELF.videoMSE.readyState == 'open') {
                var sbLength = _SELF.videoMSE.sourceBuffers.length
                for (var i = 0; i < sbLength; i++) {
                  _SELF.videoMSE.removeSourceBuffer(_SELF.videoMSE.sourceBuffers[0])
                }

                _SELF.videoMSE.endOfStream()
              }

              _SELF.audioSegments = { audio: [] }
              _SELF.videoSegments = { video: [] }

              _SELF.video_object.pause()
              _SELF.audio_object.pause()
              if (_SELF.audioMSE) {
                _SELF.audio_object.src = URL.createObjectURL(_SELF.audioMSE)
              }
              if (_SELF.videoMSE) {
                _SELF.video_object.src = URL.createObjectURL(_SELF.videoMSE)
              }
              _SELF.video_object.load()
              _SELF.audio_object.load()

              _SELF.event_callback.call(_SELF, {
                action: 'stream.stop',
                type: 'vod',
                instance: _SELF,
                data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
              })

              _vod.isPlaying = false
            }

            _SELF.videosb = { video: null }
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excep error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      /**
             * @desc 修改窗口宽高大小
             * @params
             *  - width 窗口宽度，可选
             *  - height 窗口高度，可选
             * @remark
             *  - width、height不传值，默认窗口容器宽高
             */
      this.Resize = function (width, height) {
        try {
          var fn = 'WND#Resize'

          var __width = null
          var __height = null

          if (width == null || typeof width === 'undefined') {
            __width = null
          } else {
            __width = width
          }
          if (height == null || typeof height === 'undefined') {
            __height = null
          } else {
            __height = height
          }

          var cot_dimen = {
            width: (parseInt(this.container.offsetWidth) || '100%'),
            height: (parseInt(this.container.offsetHeight) || '100%')
          }

          if (__width == null) {
            __width = cot_dimen.width
          }
          if (__height == null) {
            __height = cot_dimen.height
          }

          __width = String(__width || 0).toLowerCase()
          __height = String(__height || 0).toLowerCase()

          if (__width.search('%') == -1 && __width.search('px') == -1 && __width.search('em') == -1) {
            __width += 'px'
          }
          if (__height.search('%') == -1 && __height.search('px') == -1 && __height.search('em') == -1) {
            __height += 'px'
          }

          if (this.video_object) {
            this.video_object.style.width = __width
            this.video_object.style.height = __height
          }

          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
        } catch (e) {
          H5Live.Utils.Log(fn + ' -> Resize', 'excp error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      this.AddSourceBuffer = function (type) {
        try {
          var fn = 'WND#AddSourceBuffer'

          var mimetype = type + '/mp4; codecs="' + _SELF.metadata[type + 'Codec'] + '"'

          var issurpported = MediaSource.isTypeSupported(mimetype)
          if (!issurpported) {
            return
          }
          var sb = null
          if (type == 'audio') {
            if (_SELF.audiosb[type] != null) {
              return
            }
            sb = _SELF.audiosb[type] = _SELF.audioMSE.addSourceBuffer(mimetype)
          } else {
            if (_SELF.videosb[type] != null) {
              return
            }
            sb = _SELF.videosb[type] = _SELF.videoMSE.addSourceBuffer(mimetype)
          }
          sb.type = type

          sb.addEventListener('updateend', function (e) {
            var type = e.target.type

            if (false) {
              if (type == 'audio') {
                if (!_SELF.audioMSE || _SELF.audioMSE.readyState !== 'open') {
                  return
                }
                if (!_SELF.audioSegments.audio.length) {
                  _SELF.audioMSE.endOfStream()
                  return
                }
              } else {
                if (!_SELF.videoMSE || _SELF.videoMSE.readyState !== 'open') {
                  return
                }
                if (!_SELF.videoSegments.video.length) {
                  _SELF.videoMSE.endOfStream()
                  return
                }
              }
            }

            if (type == 'audio') {
              var sb2 = _SELF.audiosb[type]
            } else {
              var sb2 = _SELF.videosb[type]
            }

            if (!sb2 || sb2.updating) {
              return
            }
            if (type == 'audio') {
              var seg = _SELF.audioSegments[type].shift()
            } else {
              var seg = _SELF.videoSegments[type].shift()
            }
            try {
              sb2.appendBuffer(seg)
            } catch (err) {
            }
          })
          sb.addEventListener('error', function (e) {
            H5Live.Utils.Log('source buffer error')
          })
        } catch (e) {
          H5Live.Utils.Log('WND::AddSourceBuffer', 'excp error = ' + e.name + ',' + e.message)
        }
      }

      /**
			 * 视频抓图
			 * Canvas复制元素内容，转成base64后创建下载
			 * @基础支持：
			 * 		Desktop
			 *			IE: 9+
			 *			Chrome:	4+
			 *			Firefox:  3.6+(Gecko内核1.9.2)
			 *			Opera:	9+
			 *			Safari:	4.0+
			 *		Mobile
			 *			Android:	3.2+
			 *			Chrome For Android: 18+
			 *			Firefox For Android: 1.0+(Gecko内核1.9.2)
			 *			IE Mobile：0+
			 *			Opera Mobile: 19+
			 *			Safari Mobile: 3.0+
			 */
      this.Snapshot = function (options) {
        if (typeof options === "undefined") {
          options = {}
        }

        if (typeof options.imageType === "undefined") {
          options.imageType = 'image/png'
        }

        // @huzw 2019.06.29 add
        if (!options.saveAsName) {
          options.saveAsName = 'Snapshot_' + new Date().getTime()
        }

        // 先创建画布
        var canvas = document.createElement('canvas')
        var canvasCtx = canvas.getContext("2d")

        canvas.width = this.video_object.videoWidth || 1280
        canvas.height = this.video_object.videoHeight || 768

        canvasCtx.drawImage(
          this.video_object,
          0,
          0,
          this.video_object.videoWidth,
          this.video_object.videoHeight,
          0,
          0,
          this.video_object.videoWidth,
          this.video_object.videoHeight
        )

        if (window.navigator.msSaveOrOpenBlob) {
          // IE和Edge 浏览器使用
          var imgData = canvas.msToBlob(options.imageType)
          var blobObj = new Blob([imgData])
          window.navigator.msSaveOrOpenBlob(blobObj, options.saveAsName+'.PNG')
        }
        else {
          var dataUrl = canvas.toDataURL(options.imageType)
          var aEl = document.createElement('a')
          aEl.href = dataUrl
          aEl.download = options.saveAsName
          aEl.style.display = "none"
          aEl.addEventListener('click', function(e){
            e.stopPropagation()
          }, false)
          document.body.appendChild(aEl)
          aEl.click()
          document.body.removeChild(aEl)
        }
      }

      // 开启本地录像
      this.OpenRecord = function (options) {
        try {
          var fn = 'WND#OpenRecord'

          options = options || {}
          if (!_SELF.video.isPlaying || _SELF.record.isRecording) {
            return false
          }
          var _record = _SELF.record
          _record.puid = _SELF.video.puid
          _record.idx = _SELF.video.idx || 0
          _record.session = options.sessionID + '-' + _record.puid + '-' + _record.idx + '-' + new Date().getTime()

          _record.ws = _SELF.video.ws

          _SELF.event_callback.call(_SELF, {
            action: 'record.start',
            type: 'video',
            instance: _SELF,
            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
          })

          var request_xml = H5Live.Utils.tpl(
            '<DstRes Type="IV" Idx="{#idx}" PUID="{#puid}" OptID="CTL_HTTPLite_FileRecord">' +
            '   <Param Token="{#session}" Status="Start" ></Param>' +
            '</DstRes>', {
              puid: _record.puid,
              idx: _record.idx,
              session: _record.session
            }
          )

          var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
          _record.ws.send(send_buffer)
          _SELF.record.isRecording = true       //未响应直接修改录像状态
        } catch (e) {
          H5Live.Utils.Log(fn, 'excep error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      // 关闭本地录像
      this.CloseRecord = function () {
        try {
          var fn = 'WND#OpenRecord'

          if (!_SELF.video.isPlaying || !_SELF.record.isRecording) {
            return false
          }

          var _record = _SELF.record

          _SELF.event_callback.call(_SELF, {
            action: 'record.stop',
            type: 'video',
            instance: _SELF,
            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
          })

          var request_xml = H5Live.Utils.tpl(
            '<DstRes Type="IV" Idx="{#idx}" PUID="{#puid}" OptID="CTL_HTTPLite_FileRecord">' +
            '   <Param Token="{#session}" Status="Stop" ></Param>' +
            '</DstRes>', {
              puid: _record.puid,
              idx: _record.idx,
              session: _record.session
            }
          )

          var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
          _record.ws.send(send_buffer)
          _SELF.record.isRecording = false
        } catch (e) {
          H5Live.Utils.Log(fn, 'excep error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      this.customParams = customParams || {}

      this.Resize('100%', '100%')

      this.errorCode = H5Live.ErrorCode.NRCAP_SUCCESS

      // 记录到全局窗口信息中
      H5Live.windowObjects.set(this.key, this)
      return true
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return false
    }
  },

  Call: function (mediaStream, customParams) {
    try {
      var fn = 'H5Live.Call'

      this.call = {
        containerId: null,
        puid: null,
        idx: null,
        type: 'OA',
        ws: null,
        isPlaying: false,
        _cb: null
      }

      this.call.containerId = customParams.containerOrId

      /*this.key = this.container.id;

            this.windowObjects = H5Live.windowObjects.get(this.key); */

      this.ObjectStore = {
        AudioContext: window.AudioContext
      }

      this.track = mediaStream.getTracks()[0]

      this.customParams = customParams || {}
      var container = null
      if (typeof customParams.containerOrId === 'object' && typeof customParams.containerOrId.id !== 'undefined') {
        container = customParams.containerOrId
      } else {
        if (customParams.containerOrId == null || typeof customParams.containerOrId === 'undefined' || document.getElementById(customParams.containerOrId) == null) {
          H5Live.Utils.Log(fn, 'containerOrId error~')
          return false
        }

        container = document.getElementById(customParams.containerOrId)
      }

      this.container = container || null
      this.event_callback = customParams.callback || function () { }
      // variables
      this.deviceSampleRate = 48000 // range: 22050 to 96000
      this.audioChannels = 1

      if (!this.ObjectStore.AudioContextConstructor) {
        this.ObjectStore.AudioContextConstructor = new this.ObjectStore.AudioContext()
      }

      // check device sample rate
      this.deviceSampleRate = this.ObjectStore.AudioContextConstructor.sampleRate

      this.leftchannel = []
      this.rightchannel = []
      this.scriptprocessornode = null
      this.recording = false
      this.recordingLength = 0
      this.sampleRate = this.deviceSampleRate

      this.mimeType = 'audio/wav' // 'audio/ogg; codecs=opus';
      this.isPCM = this.mimeType.indexOf('audio/pcm') > -1

      this.numChannels = this.audioChannels || 2

      this.RequestData = function () {
        try {
          var fn = 'CALL#RequestData'

          if (_SELF.isPaused) {
            return
          }
          if (_SELF.recordingLength === 0) {
            _SELF.requestDataInvoked = false
            return
          }
          _SELF.requestDataInvoked = true
          // clone stuff
          var internalLeftChannel = _SELF.leftchannel.slice(0)
          var internalRightChannel = _SELF.rightchannel.slice(0)
          var internalRecordingLength = _SELF.recordingLength

          // reset the buffers for the new recording
          _SELF.leftchannel.length = _SELF.rightchannel.length = []
          _SELF.recordingLength = 0
          _SELF.requestDataInvoked = false

          // we flat the left and right channels down
          var leftBuffer = _SELF.MergeBuffers(internalLeftChannel, internalRecordingLength)

          var interleaved = leftBuffer

          // we interleave both channels together
          if (_SELF.numChannels === 2) {
            var rightBuffer = _SELF.MergeBuffers(internalRightChannel, internalRecordingLength) // bug fixed via #70,#71
            interleaved = _SELF.Interleave(leftBuffer, rightBuffer)
          }

          if (_SELF.isPCM) {
            // our final binary blob
            var blob = new Blob([_SELF.ConvertoFloat32ToInt16(interleaved)], {
              type: 'audio/pcm'
            })
            _SELF.OnDataAvailable(blob)
            return
          }

          // we create our wav file
          var buffer = new ArrayBuffer(44 + interleaved.length * 2)
          var view = new DataView(buffer)

          // RIFF chunk descriptor
          this.WriteUTFBytes(view, 0, 'RIFF')

          // -8 (via #97)
          view.setUint32(4, 44 + interleaved.length * 2 - 8, true)

          this.WriteUTFBytes(view, 8, 'WAVE')
          // FMT sub-chunk
          this.WriteUTFBytes(view, 12, 'fmt ')
          view.setUint32(16, 16, true)
          view.setUint16(20, 1, true)
          // stereo (2 channels)
          view.setUint16(22, this.numChannels, true)
          view.setUint32(24, this.sampleRate, true)
          view.setUint32(28, this.sampleRate * this.numChannels * 2, true) // this.numChannels * 2 (via #71)
          view.setUint16(32, this.numChannels * 2, true)
          view.setUint16(34, 16, true)
          // data sub-chunk
          this.WriteUTFBytes(view, 36, 'data')
          view.setUint32(40, interleaved.length * 2, true)

          // write the PCM samples
          var lng = interleaved.length
          var index = 44
          var volume = 1
          for (var i = 0; i < lng; i++) {
            view.setInt16(index, interleaved[i] * (0x7FFF * volume), true)
            index += 2
          }

          // our final binary blob
          var blob = new Blob([view], {
            type: 'audio/wav'
          })

          _SELF.OnDataAvailable(blob)
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.OnDataAvailable = function (data) {
        try {
          var fn = 'CALL#OnDataAvailable'

          if (data && parseInt(_SELF.call.ws.readyState) === 1) {
            _SELF.blobs.push(data)
            _SELF.call.ws.send(data)
            _SELF.call.isPlaying = true
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.OpenCall = function (options) {
        try {
          var fn = 'CALL#OpenCall'
          _SELF.recording = true
          // reset the buffers for the new recording
          _SELF.leftchannel.length = _SELF.rightchannel.length = 0
          _SELF.recordingLength = 0

          _SELF.options = options || {}
          _SELF.timeSlice = 100

          _SELF.call.puid = options.puid
          _SELF.call.type = 'OA'
          _SELF.call.idx = options.idx
          _SELF.call.streamType = options.streamType || 'REALTIME'

          _SELF.call.ws = new WebSocket(_SELF.options.wsPath)
          _SELF.call.ws.binaryType = 'arraybuffer'

          _SELF.call.ws.onopen = function (e) {
            console.log('连接服务器成功')

            _SELF.event_callback.call(_SELF, {
              action: 'ws.open',
              type: 'call',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })

            var request_xml = '<DstRes Type="OA" Idx="' + _SELF.options.idx + '" PUID="' + _SELF.options.puid + '" OptID="CTL_OA_StartCall_PushMode">' +
              '   <Param StreamType="' + _SELF.options.streamType + '" Mixed="1"></Param>' +
              '</DstRes>'

            var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
            _SELF.call.ws.send(send_buffer)

            _SELF.interval = setInterval(function () { _SELF.RequestData() }, _SELF.timeSlice)
          }
          _SELF.call.ws.onclose = function (e) {
            console.log('call websocket 连接关闭')
            console.log(e)
            _SELF.event_callback.call(_SELF, {
              action: 'ws.close',
              type: 'call',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
            if (_SELF.call.isPlaying) {
              _SELF.call.isPlaying = false
              _SELF.CloseCall()
            }
          }
          _SELF.call.ws.onerror = function (e) {
            console.log('call websocket 连接出错')
            console.log(e)
            _SELF.event_callback.call(_SELF, {
              action: 'ws.error',
              type: 'call',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
            console.log('连接出错')
          }
          _SELF.call.ws.onmessage = function (e) {

          }

          _SELF.event_callback.call(_SELF, {
            action: 'stream.start',
            type: 'call',
            instance: _SELF,
            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
          })
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.CloseCall = function () {
        try {
          var fn = 'CALL#CloseCall'

          if (_SELF.call.isPlaying) {
            _SELF.call.isPlaying = false
            _SELF.call.ws.close()
          }

          _SELF.recording = false
          clearInterval(_SELF.interval)
          _SELF.RequestData()

          _SELF.audioInput.disconnect()
          _SELF.onstop()

          _SELF.track.stop()
          _SELF.event_callback.call(_SELF, {
            action: 'stream.stop',
            type: 'call',
            instance: _SELF,
            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
          })
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.BytesToSize = function (bytes) {
        try {
          var fn = 'CALL#BytesToSize'
          var k = 1000
          var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
          if (bytes === 0) {
            return '0 Bytes'
          }
          var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10)
          return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.MergeBuffers = function (channelBuffer, recordingLength) {
        try {
          var fn = 'CALL#MergeBuffers'
          var result = new Float32Array(recordingLength)
          var offset = 0
          var lng = channelBuffer.length
          for (var i = 0; i < lng; i++) {
            var buffer = channelBuffer[i]
            result.set(buffer, offset)
            offset += buffer.length
          }
          return result
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.WriteUTFBytes = function (view, offset, string) {
        try {
          var fn = 'CALL#WriteUTFBytes'
          var lng = string.length
          for (var i = 0; i < lng; i++) {
            view.setUint8(offset + i, string.charCodeAt(i))
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.ConvertoFloat32ToInt16 = function (buffer) {
        try {
          var fn = 'CALL#ConvertoFloat32ToInt16'

          var l = buffer.length
          var buf = new Int16Array(l)

          while (l--) {
            buf[l] = buffer[l] * 0xFFFF // convert to 16 bit
          }
          return buf.buffer
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      // creates the audio context
      this.context = this.ObjectStore.AudioContextConstructor

      // creates a gain node
      this.ObjectStore.VolumeGainNode = this.context.createGain()

      this.volume = this.ObjectStore.VolumeGainNode

      // creates an audio node from the microphone incoming stream
      this.ObjectStore.AudioInput = this.context.createMediaStreamSource(mediaStream)

      // creates an audio node from the microphone incoming stream
      this.audioInput = this.ObjectStore.AudioInput

      // connect the stream to the gain node
      this.audioInput.connect(this.volume)

      /* From the spec: This value controls how frequently the audioprocess event is
            dispatched and how many sample-frames need to be processed each call.
            Lower values for buffer size will result in a lower (better) latency.
            Higher values will be necessary to avoid audio breakup and glitches
            Legal values are 256, 512, 1024, 2048, 4096, 8192, and 16384. */
      this.bufferSize = 2048
      if (this.bufferSize === 0) {
        this.bufferSize = 0
      }

      if (this.context.createJavaScriptNode) {
        this.scriptprocessornode = this.context.createJavaScriptNode(this.bufferSize, this.numChannels, this.numChannels)
      } else if (this.context.createScriptProcessor) {
        this.scriptprocessornode = this.context.createScriptProcessor(this.bufferSize, this.numChannels, this.numChannels)
      } else {
        throw 'WebAudio API has no support on this browser.'
      }

      this.bufferSize = this.scriptprocessornode.bufferSize

      console.debug('using audio buffer-size:', this.bufferSize)

      this.requestDataInvoked = false

      // sometimes "scriptprocessornode" disconnects from he destination-node
      // and there is no exception thrown in this case.
      // and obviously no further "ondataavailable" events will be emitted.
      // below global-scope variable is added to debug such unexpected but "rare" cases.
      window.scriptprocessornode = this.scriptprocessornode

      if (this.numChannels === 1) {
        console.debug('All right-channels are skipped.')
      }

      this.isPaused = false

      this.onstop = function () {
        console.log('stopped..')
      }

      // http://webaudio.github.io/web-audio-api/#the-scriptprocessornode-interface
      this.scriptprocessornode.onaudioprocess = function (e) {
        if (!_SELF.recording || _SELF.requestDataInvoked || _SELF.isPaused) {
          return
        }

        var left = e.inputBuffer.getChannelData(0)
        _SELF.leftchannel.push(new Float32Array(left))

        if (_SELF.numChannels === 2) {
          var right = e.inputBuffer.getChannelData(1)
          _SELF.rightchannel.push(new Float32Array(right))
        }
        _SELF.recordingLength += _SELF.bufferSize
      }

      this.volume.connect(this.scriptprocessornode)
      this.scriptprocessornode.connect(this.context.destination)

      this.options = {}
      this.ws = null
      this.blobs = []
      this.interval = null
      var _SELF = this
      H5Live.callObject = this
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return false
    }
  },

  Talk: function (mediaStream, customParams) {
    try {
      var fn = 'H5Live.Talk'

      this.talk = {
        containerId: null,
        puid: null,
        idx: null,
        type: 'OA',
        ws: null,
        isPlaying: false,
        _cb: null
      }

      this.audio = {
        containerId: null,
        isPlaying: false,
        _cb: null
      }

      this.ObjectStore = {
        AudioContext: window.AudioContext
      }

      this.track = mediaStream.getTracks()[0]

      //this.talk.containerId = this.containerId
      this.talk.containerId = customParams.containerOrId

      this.audioMSE = new MediaSource()
      this.audioSegments = { audio: [] }
      this.audiosb = { audio: null }
      this.metadata = {
        audioCodec: 'mp4a.40.5'
      }

      this.audioMSE.addEventListener('sourceopen', function (e) {
        _SELF.AddSourceBuffer('audio')
      })

      this.customParams = customParams || {}

      var container = null
      if (typeof customParams.containerOrId === 'object' && typeof customParams.containerOrId.id !== 'undefined') {
        container = customParams.containerOrId
      } else {
        if (customParams.containerOrId == null || typeof customParams.containerOrId === 'undefined' || document.getElementById(customParams.containerOrId) == null) {
          H5Live.Utils.Log(fn, 'containerOrId error~')
          return false
        }

        container = document.getElementById(customParams.containerOrId)
      }

      this.container = container || null
      this.event_callback = customParams.callback || function () { }

      var t_wndid = container.id
      var t_wndname = container.id
      this.audio_object = container.lastChild || document.getElementById(t_wndid + '_audio')

      /* var _winstance = H5Live.windowObjects.get(t_wndid);
            if(_winstance.audio.isPlaying){
                var r=confirm("启动对讲前需要停止播放声音和喊话，确定要停止播放声音和喊话并开始对讲吗？")
                if (r==true){
                    _winstance.CloseAudio();
                }
            }*/

      this.audio_object.src = URL.createObjectURL(this.audioMSE)
      this.audio_object.load()

      // variables
      this.deviceSampleRate = 48000 // range: 22050 to 96000
      this.audioChannels = 1

      if (!this.ObjectStore.AudioContextConstructor) {
        this.ObjectStore.AudioContextConstructor = new this.ObjectStore.AudioContext()
      }

      // check device sample rate
      this.deviceSampleRate = this.ObjectStore.AudioContextConstructor.sampleRate

      this.leftchannel = []
      this.rightchannel = []
      this.scriptprocessornode = null
      this.recording = false
      this.recordingLength = 0
      this.sampleRate = this.deviceSampleRate

      this.mimeType = 'audio/wav' // 'audio/ogg; codecs=opus';
      this.isPCM = this.mimeType.indexOf('audio/pcm') > -1

      this.numChannels = this.audioChannels || 2

      this.RequestData = function () {
        try {
          var fn = 'TALK#RequestData'
          if (_SELF.isPaused) {
            return
          }
          if (_SELF.recordingLength === 0) {
            _SELF.requestDataInvoked = false
            return
          }
          _SELF.requestDataInvoked = true
          // clone stuff
          var internalLeftChannel = _SELF.leftchannel.slice(0)
          var internalRightChannel = _SELF.rightchannel.slice(0)
          var internalRecordingLength = _SELF.recordingLength

          // reset the buffers for the new recording
          _SELF.leftchannel.length = _SELF.rightchannel.length = []
          _SELF.recordingLength = 0
          _SELF.requestDataInvoked = false

          // we flat the left and right channels down
          var leftBuffer = _SELF.MergeBuffers(internalLeftChannel, internalRecordingLength)

          var interleaved = leftBuffer

          // we interleave both channels together
          if (_SELF.numChannels === 2) {
            var rightBuffer = _SELF.MergeBuffers(internalRightChannel, internalRecordingLength) // bug fixed via #70,#71
            interleaved = _SELF.Interleave(leftBuffer, rightBuffer)
          }

          if (_SELF.isPCM) {
            // our final binary blob
            var blob = new Blob([_SELF.ConvertoFloat32ToInt16(interleaved)], {
              type: 'audio/pcm'
            })
            _SELF.OnDataAvailable(blob)
            return
          }

          // we create our wav file
          var buffer = new ArrayBuffer(44 + interleaved.length * 2)
          var view = new DataView(buffer)

          // RIFF chunk descriptor
          this.WriteUTFBytes(view, 0, 'RIFF')

          // -8 (via #97)
          view.setUint32(4, 44 + interleaved.length * 2 - 8, true)

          this.WriteUTFBytes(view, 8, 'WAVE')
          // FMT sub-chunk
          this.WriteUTFBytes(view, 12, 'fmt ')
          view.setUint32(16, 16, true)
          view.setUint16(20, 1, true)
          // stereo (2 channels)
          view.setUint16(22, this.numChannels, true)
          view.setUint32(24, this.sampleRate, true)
          view.setUint32(28, this.sampleRate * this.numChannels * 2, true) // this.numChannels * 2 (via #71)
          view.setUint16(32, this.numChannels * 2, true)
          view.setUint16(34, 16, true)
          // data sub-chunk
          this.WriteUTFBytes(view, 36, 'data')
          view.setUint32(40, interleaved.length * 2, true)

          // write the PCM samples
          var lng = interleaved.length
          var index = 44
          var volume = 1
          for (var i = 0; i < lng; i++) {
            view.setInt16(index, interleaved[i] * (0x7FFF * volume), true)
            index += 2
          }

          // our final binary blob
          var blob = new Blob([view], {
            type: 'audio/wav'
          })
          _SELF.OnDataAvailable(blob)
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.OnDataAvailable = function (data) {
        try {
          var fn = 'TALK#OnDataAvailable'

          if (data && _SELF.talk.ws && parseInt(_SELF.talk.ws.readyState) === 1) {
            _SELF.blobs.push(data)
            _SELF.talk.ws.send(data)
            _SELF.talk.isPlaying = true
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.OpenTalk = function (options) {
        try {
          var fn = 'TALK#OpenTalk'

          if (_SELF.audio.isPlaying) {
            _SELF.CloseAudio()
          }
          _SELF.audio.isPlaying = true
          _SELF.audioSegments = { audio: [] }
          // _SELF.audio_object.removeAttribute("muted");
          /* var promiseAudio = _SELF.audio_object.play();
                    if (promiseAudio) {
                        promiseAudio['catch'](function(e) {});
                    }

                    _SELF.event_callback.call(_SELF, {
                        action: 'talk.start',
                        type: 'talk',
                        instance: _SELF,
                        data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
                    });*/

          _SELF.recording = true
          // reset the buffers for the new recording
          _SELF.leftchannel.length = _SELF.rightchannel.length = 0
          _SELF.recordingLength = 0

          _SELF.options = options || {}
          _SELF.timeSlice = 100

          _SELF.talk.puid = options.puid
          _SELF.talk.type = 'OA'
          _SELF.talk.idx = options.idx
          _SELF.talk.streamType = options.streamType || 'REALTIME'

          _SELF.talk.ws = new WebSocket(_SELF.options.wsPath)
          _SELF.talk.ws.binaryType = 'arraybuffer'

          _SELF.talk.ws.onopen = function (e) {
            console.log('连接服务器成功')

            _SELF.event_callback.call(_SELF, {
              action: 'ws.open',
              type: 'talk',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })

            var request_xml = '<DstRes Type="OA" Idx="' + _SELF.options.idx + '" PUID="' + _SELF.options.puid + '" OptID="CTL_OA_StartTalk_PushMode">' +
              '   <Param StreamType="' + _SELF.options.streamType + '" Mixed="1"></Param>' +
              '</DstRes>'
            var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
            _SELF.talk.ws.send(send_buffer)


            var promiseAudio = _SELF.audio_object.play()
            if (promiseAudio) {
              promiseAudio['catch'](function (e) { })
            }
            _SELF.AddSourceBuffer('audio')

            _SELF.event_callback.call(_SELF, {
              action: 'stream.start',
              type: 'talk',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
            })

            _SELF.interval = setInterval(function () { _SELF.RequestData() }, _SELF.timeSlice)
          }
          _SELF.talk.ws.onclose = function (e) {
            console.log('talk websocket 连接关闭')
            console.log(e)
            _SELF.event_callback.call(_SELF, {
              action: 'ws.close',
              type: 'talk',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
            if (_SELF.talk.isPlaying) {
              _SELF.talk.isPlaying = false
              _SELF.CloseTalk()
            }
          }
          _SELF.talk.ws.onerror = function (e) {
            console.log('talk websocket 连接出错')
            console.log(e)
            _SELF.event_callback.call(_SELF, {
              action: 'ws.error',
              type: 'talk',
              instance: _SELF,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
            console.log('连接出错')
          }
          _SELF.talk.ws.onmessage = function (e) {
            var data = new Uint8Array(e.data)
            var pos = 0
            var type = data[pos++]

            switch (type) {
              case 0x08:
                var segtype = type = 'audio'
                var seg = new Uint8Array(e.data, pos)

                // pos += 3; // skip 3 bytes of command
                pos += 4 // skip 4 bytes of box size

                var evttype = 'playeaseMp4Segment'
                if (data[pos] === 0x66 && data[pos + 1] === 0x74 && data[pos + 2] === 0x79 && data[pos + 3] === 0x70) { // is ftyp box
                  evttype = 'playeaseMp4InitSegment'
                }

                var data = {
                  data: seg,
                  tp: segtype,
                  type: evttype
                }

                if (evttype == 'playeaseMp4Segment') {
                  data.data.info = data.info
                  _SELF.audioSegments[data.tp].push(data.data)

                  if (_SELF.audioSegments[data.tp].length == 0) {
                    return
                  }

                  var sb = _SELF.audiosb[data.tp]
                  if (!sb || sb.updating) {
                    return
                  }
                  var seg = _SELF.audioSegments[data.tp].shift()
                  try {
                    sb.appendBuffer(seg)
                  } catch (err) {
                  }
                } else if (evttype == 'playeaseMp4InitSegment') {
                  _SELF.audio_object.pause()

                  if (_SELF.audioMSE && _SELF.audioMSE.readyState == 'open') {
                    var sbLength = _SELF.audioMSE.sourceBuffers.length
                    for (var i = 0; i < sbLength; i++) {
                      _SELF.audioMSE.removeSourceBuffer(_SELF.audioMSE.sourceBuffers[0])
                    }

                    _SELF.audioMSE.endOfStream()
                    _SELF.audioMSE = null
                  }

                  _SELF.audioMSE = new MediaSource()
                  _SELF.audioSegments = { audio: [] }
                  _SELF.audiosb = { audio: null }
                  _SELF.audioMSE.addEventListener('sourceopen', function (e) {

                    var promiseAudio = _SELF.audio_object.play()
                    if (promiseAudio) {
                      promiseAudio['catch'](function (e) { })
                    }

                    _SELF.AddSourceBuffer('audio')
                  })

                  _SELF.audio_object.pause()
                  if (_SELF.audioMSE) {
                    _SELF.audio_object.src = URL.createObjectURL(_SELF.audioMSE)
                  }
                  _SELF.audio_object.load()
                  _SELF.audioSegments[data.tp].push(data.data)
                }
                break
              default:
                H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
                break
            }
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.CloseTalk = function () {
        try {
          var fn = 'TALK#CloseTalk'

          if (_SELF.audio_object != null) {
            if (_SELF.talk.ws) {
              _SELF.talk.ws.close()
              _SELF.talk.ws = null
            }

            if (_SELF.talk.isPlaying) {
              if (_SELF.audioMSE && _SELF.audioMSE.readyState == 'open') {
                var sbLength = _SELF.audioMSE.sourceBuffers.length
                for (var i = 0; i < sbLength; i++) {
                  _SELF.audioMSE.removeSourceBuffer(_SELF.audioMSE.sourceBuffers[0])
                }

                _SELF.audioMSE.endOfStream()
              }

              _SELF.audioSegments = { audio: [] }

              _SELF.audio_object.pause()
              if (_SELF.audioMSE) {
                _SELF.audio_object.src = URL.createObjectURL(_SELF.audioMSE)
              }
              _SELF.audio_object.load()

              // _SELF.audio_object.attr("muted",true);

              _SELF.recording = false
              clearInterval(_SELF.interval)
              _SELF.RequestData()

              _SELF.audioInput.disconnect()
              _SELF.onstop()

              _SELF.track.stop()

              _SELF.event_callback.call(_SELF, {
                action: 'stream.stop',
                type: 'talk',
                instance: _SELF,
                data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS)
              })

              _SELF.audio.isPlaying = false
            }

            _SELF.audiosb = { audio: null }
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      this.AddSourceBuffer = function (type) {
        try {
          var fn = 'TALK#AddSourceBuffer'

          var mimetype = type + '/mp4; codecs="' + _SELF.metadata[type + 'Codec'] + '"'

          var issurpported = MediaSource.isTypeSupported(mimetype)
          if (!issurpported) {
            return
          }
          var sb = null
          if (_SELF.audiosb[type] != null) {
            return
          }
          sb = _SELF.audiosb[type] = _SELF.audioMSE.addSourceBuffer(mimetype)
          sb.type = type

          sb.addEventListener('updateend', function (e) {
            var type = e.target.type

            if (false) {
              if (!_SELF.audioMSE || _SELF.audioMSE.readyState !== 'open') {
                return
              }
              if (!_SELF.audioSegments.audio.length) {
                _SELF.audioMSE.endOfStream()
                return
              }
            }

            var sb2 = _SELF.audiosb[type]

            if (!sb2 || sb2.updating) {
              return
            }

            var seg = _SELF.audioSegments[type].shift()
            try {
              sb2.appendBuffer(seg)
            } catch (err) {
            }
          })
          sb.addEventListener('error', function (e) {
            H5Live.Utils.Log('source buffer error')
          })
        } catch (e) {
          H5Live.Utils.Log('WND::AddSourceBuffer', 'excp error = ' + e.name + ',' + e.message)
        }
      }

      this.Interleave = function (leftChannel, rightChannel) {
        try {
          var fn = 'TALK#Interleave'

          var length = leftChannel.length + rightChannel.length
          var result = new Float32Array(length)

          var inputIndex = 0

          for (var index = 0; index < length;) {
            result[index++] = leftChannel[inputIndex]
            result[index++] = rightChannel[inputIndex]
            inputIndex++
          }
          return result
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.MergeBuffers = function (channelBuffer, recordingLength) {
        try {
          var fn = 'TALK#MergeBuffers'

          var result = new Float32Array(recordingLength)
          var offset = 0
          var lng = channelBuffer.length
          for (var i = 0; i < lng; i++) {
            var buffer = channelBuffer[i]
            result.set(buffer, offset)
            offset += buffer.length
          }
          return result
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.WriteUTFBytes = function (view, offset, string) {
        try {
          var fn = 'TALK#WriteUTFBytes'

          var lng = string.length
          for (var i = 0; i < lng; i++) {
            view.setUint8(offset + i, string.charCodeAt(i))
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.ConvertoFloat32ToInt16 = function (buffer) {
        try {
          var fn = 'TALK#ConvertoFloat32ToInt16'

          var l = buffer.length
          var buf = new Int16Array(l)

          while (l--) {
            buf[l] = buffer[l] * 0xFFFF // convert to 16 bit
          }
          return buf.buffer
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      // creates the audio context
      this.context = this.ObjectStore.AudioContextConstructor

      // creates a gain node
      this.ObjectStore.VolumeGainNode = this.context.createGain()

      this.volume = this.ObjectStore.VolumeGainNode

      // creates an audio node from the microphone incoming stream
      this.ObjectStore.AudioInput = this.context.createMediaStreamSource(mediaStream)

      // creates an audio node from the microphone incoming stream
      this.audioInput = this.ObjectStore.AudioInput

      // connect the stream to the gain node
      this.audioInput.connect(this.volume)

      /* From the spec: This value controls how frequently the audioprocess event is
            dispatched and how many sample-frames need to be processed each call.
            Lower values for buffer size will result in a lower (better) latency.
            Higher values will be necessary to avoid audio breakup and glitches
            Legal values are 256, 512, 1024, 2048, 4096, 8192, and 16384. */
      this.bufferSize = 2048
      if (this.bufferSize === 0) {
        this.bufferSize = 0
      }

      if (this.context.createJavaScriptNode) {
        this.scriptprocessornode = this.context.createJavaScriptNode(this.bufferSize, this.numChannels, this.numChannels)
      } else if (this.context.createScriptProcessor) {
        this.scriptprocessornode = this.context.createScriptProcessor(this.bufferSize, this.numChannels, this.numChannels)
      } else {
        throw 'WebAudio API has no support on this browser.'
      }

      this.bufferSize = this.scriptprocessornode.bufferSize

      console.debug('using audio buffer-size:', this.bufferSize)

      this.requestDataInvoked = false

      // sometimes "scriptprocessornode" disconnects from he destination-node
      // and there is no exception thrown in this case.
      // and obviously no further "ondataavailable" events will be emitted.
      // below global-scope variable is added to debug such unexpected but "rare" cases.
      window.scriptprocessornode = this.scriptprocessornode

      if (this.numChannels === 1) {
        console.debug('All right-channels are skipped.')
      }

      this.isPaused = false

      this.onstop = function () {
        console.log('stopped..')
      }

      // http://webaudio.github.io/web-audio-api/#the-scriptprocessornode-interface
      this.scriptprocessornode.onaudioprocess = function (e) {
        if (!_SELF.recording || _SELF.requestDataInvoked || _SELF.isPaused) {
          return
        }

        var left = e.inputBuffer.getChannelData(0)
        _SELF.leftchannel.push(new Float32Array(left))

        if (_SELF.numChannels === 2) {
          var right = e.inputBuffer.getChannelData(1)
          _SELF.rightchannel.push(new Float32Array(right))
        }
        _SELF.recordingLength += _SELF.bufferSize
      }

      this.volume.connect(this.scriptprocessornode)
      this.scriptprocessornode.connect(this.context.destination)

      this.options = {}
      this.ws = null
      this.blobs = []
      this.interval = null
      var _SELF = this
      H5Live.talkObject = this
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return false
    }
  },

  //命令交互，用于创建主websocket，接收和发送主websocket的命令
  CommandCommunicate: function (customParams) {
    try {
      var fn = 'H5Live.CommandCommunicate'

      this.CommandWS = new WebSocket(customParams.wsPath)

      this.CommandWS.binaryType = 'arraybuffer'

      this.event_callback = customParams.callback

      this.CommandWS.onopen = function (e) {
        console.log('连接服务器成功')

        var request_xml = '<Register SessionID="'+ customParams.sessionID +'" UserName="'+ customParams.chatName +'" ></Register>'
        var send_buffer = H5Live._make_ws_send_buffer(14, request_xml)
        _SELF.CommandWS.send(send_buffer)

        _SELF.event_callback.call(_SELF, {
          action: 'ws.open',
          type: 'command',
          instance: _SELF,
          data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
        })
      }
      this.CommandWS.onclose = function (e) {
        console.log('command websocket 连接关闭')
        console.log(e)

        _SELF.event_callback.call(_SELF, {
          action: 'ws.close',
          type: 'command',
          instance: _SELF,
          data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
        })
      }
      this.CommandWS.onerror = function (e) {
        console.log('command websocket 连接出错')
        console.log(e)

        _SELF.event_callback.call(_SELF, {
          action: 'ws.error',
          type: 'command',
          instance: _SELF,
          data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
        })
      }
      this.CommandWS.onmessage = function (e) {
        var xmlBuf = new DataView(e.data.slice(7, e.data.byteLength - 1))
        var xmlBlob = new Blob([xmlBuf])
        var xmlReader  = new FileReader()
        xmlReader.addEventListener("load", function () {
          var xml = xmlReader.result
          var parser = new DOMParser()
          var xmlDoc = parser.parseFromString(xml,"text/xml")
          var dst = xmlDoc.getElementsByTagName("DstRes")[0]
          var action = dst.getAttribute('OptID')
          switch (action) {
            //设备向网页请求视频通话
            case 'CTL_HTTPLite_P2PRequest':
              var Param = xmlDoc.getElementsByTagName("Param")[0]
              _SELF.event_callback.call(_SELF, {
                action: 'VideoChatRequest',
                type: 'command',
                instance: _SELF,
                data: {
                  connect: Param.getAttribute('Connect'),
                  srcID: Param.getAttribute('SrcID'),
                  srcType: Param.getAttribute('SrcType'),
                  dstID: Param.getAttribute('DstID'),
                  dstType: Param.getAttribute('DstType'),
                  resType: Param.getAttribute('ResType'),
                  resIdx: Param.getAttribute('ResIdx')
                }
              })
              break
            //设备返回网页的视频通话请求响应
            case 'CTL_HTTPLite_P2PResponse':
              var Param = xmlDoc.getElementsByTagName("Param")[0]
              _SELF.event_callback.call(_SELF, {
                action: 'VideoChatResponse',
                type: 'command',
                instance: _SELF,
                data: {
                  connect: Param.getAttribute('Connect'),
                  srcID: Param.getAttribute('SrcID'),
                  srcType: Param.getAttribute('SrcType'),
                  dstID: Param.getAttribute('DstID'),
                  dstType: Param.getAttribute('DstType'),
                  resType: Param.getAttribute('ResType'),
                  resIdx: Param.getAttribute('ResIdx'),
                  errorCode: Param.getAttribute('ErrorCode')
                }
              })
              break
            case 'CTL_COMMONRES_ReqWebStream_PushMode':
              var Param = xmlDoc.getElementsByTagName("Param")[0]
              if (parseInt(Param.getAttribute('Mixed')) == 2) {
                _SELF.event_callback.call(_SELF, {
                  action: 'StartTalk',
                  type: 'command',
                  instance: _SELF,
                  data: {
                    streamType: Param.getAttribute('StreamType'),
                    mixed: Param.getAttribute('Mixed'),
                    dstType: Param.getAttribute('DstType'),
                    ip: Param.getAttribute('IP'),
                    port: Param.getAttribute('Port'),
                    token: Param.getAttribute('Token')
                  }
                })
              } else {
                _SELF.event_callback.call(_SELF, {
                  action: 'ReqWebStream',
                  type: 'command',
                  instance: _SELF,
                  data: {
                    xml: xml,
                    streamType: Param.getAttribute('StreamType'),
                    mixed: Param.getAttribute('Mixed'),
                    dstType: Param.getAttribute('DstType'),
                    ip: Param.getAttribute('IP'),
                    port: Param.getAttribute('Port'),
                    token: Param.getAttribute('Token')
                  }
                })
              }
              break
            case 'CTL_HTTPLite_GetH5ClientList':
              var Param = xmlDoc.getElementsByTagName("H5Client")
              var h5ClientArr = []
              Param.each(function(index, item) {
                h5ClientArr.push({
                  sessionID: item.getAttribute('SessionID'),
                  chatName: item.getAttribute('UserName'),
                  status: item.getAttribute('Status'),
                  nuid: item.getAttribute('NUID')
                })
              })
              _SELF.event_callback.call(_SELF, {
                action: 'GetH5ClientList',
                type: 'command',
                instance: _SELF,
                data: h5ClientArr
              })
              break
          }
        }, false)
        xmlReader.readAsText(xmlBlob,'UTF-8')
      }

      /**
       * 获取H5C客户端列表
       * @author zhangmeng
       * @date 2019年11月27日
       * @param
       *      connect     请求连接或者断开 1：连接 0：断开
       *      srcID       请求来源的PUID或SessionID
       *      srcType     来源类型 1：H5C 0：PU
       *      dstID       目标的PUID或SessionID
       *      dstType     目标类型 1：H5C 0：PU
       *      resType     子资源类型
       *      resIdx      子资源索引
       */
      this.GetH5ClientList = function () {
        try {
          var fn = 'CommandCommunicate#GetH5ClientList'

          var request_xml = '<DstRes OptID="CTL_HTTPLite_GetH5ClientList"></DstRes>'

          var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
          _SELF.CommandWS.send(send_buffer)
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      /**
       * 请求或关闭双向对讲，取决于connect
       * @author zhangmeng
       * @date 2019年11月27日
       * @param
       *      connect     请求连接或者断开 1：连接 0：断开
       *      srcID       请求来源的PUID或SessionID
       *      srcType     来源类型 1：H5C 0：PU
       *      dstID       目标的PUID或SessionID
       *      dstType     目标类型 1：H5C 0：PU
       *      resType     子资源类型
       *      resIdx      子资源索引
       */
      this.VideoChatRequest = function (param) {
        try {
          var fn = 'CommandCommunicate#VideoChatRequest'

          var request_xml = '<DstRes Type="IV" Idx="' + param.resIdx + '" PUID="' + param.dstID + '" OptID="CTL_HTTPLite_P2PRequest">' +
            '   <Param Connect="' + param.connect + '" SrcID="' + param.srcID + '" SrcType="' + param.srcType + '" DstID="' + param.dstID + '" DstType="' + param.dstType + '" ResType="' + param.resType + '" ResIdx="' + param.resIdx + '"></Param>' +
            '</DstRes>'
          var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
          _SELF.CommandWS.send(send_buffer)
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      /**
       * 应答双向视频
       * @author zhangmeng
       * @date 2019年11月27日
       * @param
       *      connect     请求连接或者断开 1：连接 0：断开
       *      srcID       请求来源的PUID或SessionID
       *      srcType     来源类型 1：H5C 0：PU
       *      dstID       目标的PUID或SessionID
       *      dstType     目标类型 1：H5C 0：PU
       *      resType     子资源类型
       *      resIdx      子资源索引
       *      errcode     是否接受，0表示接受，其他表示拒绝。
       */
      this.VideoChatResponse = function (param) {
        try {
          var fn = 'CommandCommunicate#VideoChatResponse'

          var request_xml = '<DstRes Type="IV" Idx="' + param.resIdx + '" PUID="' + param.dstID + '" OptID="CTL_HTTPLite_P2PResponse">' +
            '   <Param Connect="' + param.connect + '" SrcID="' + param.srcID + '" SrcType="' + param.srcType + '" DstID="' + param.dstID + '" DstType="' + param.dstType + '" ResType="' + param.resType + '" ResIdx="' + param.resIdx + '" ErrorCode="' + param.errorCode + '"></Param>' +
            '</DstRes>'

          var send_buffer = H5Live._make_ws_send_buffer(1, request_xml)
          _SELF.CommandWS.send(send_buffer)
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      var _SELF = this
      H5Live.commandCommunicateObject = this
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return false
    }
  },


  /**
     * @desc 采集视频流数据
     * @params
     *
     */
  GatherVideoStream: function (mediaStream, customParams) {
    try {
      var fn = 'H5Live.GatherVideoStream'

      this.videoData = {
        domID: null,
        isPlaying: false,
        chunks: [],
        ws: null,
        _cb: null
      }

      this.videoObject = {
        track: null,
        mediaRecorder: null
      }

      this.videoObject.track = mediaStream.getTracks()[0];

      this.OpenGatherVideoStream = function (options) {
        try {
          var fn = 'GatherVideoStream#OpenGatherVideoStream'

          var constraints = {video: true};

          if (typeof MediaRecorder.isTypeSupported == 'function'){
            if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) {
              var mimeType = {mimeType: 'video/webm;codecs=h264'}
            } else if (MediaRecorder.isTypeSupported('video/webm;codecs=h264')) {
              var mimeType = {mimeType: 'video/webm;codecs=h264'}
            } else  if (MediaRecorder.isTypeSupported('video/webm;codecs=vp8')) {
              // var options = {mimeType: 'video/webm;codecs=vp8'}
              alert('浏览器不支持')
              return false
            } else {
              return false
            }
            _SELF.videoObject.mediaRecorder = new MediaRecorder(mediaStream, mimeType)
          } else {
            alert('浏览器不支持')
            return false
          }

          _SELF.videoData.ws = new WebSocket(options.wsPath)

          _SELF.videoData.ws.binaryType = 'arraybuffer'

          _SELF.videoData.ws.onopen = function (e) {
            console.log('连接服务器成功')

            var send_buffer = H5Live._make_ws_send_buffer(1, options.xml)
            _SELF.videoData.ws.send(send_buffer)

            _SELF.event_callback.call(this, {
              action: 'ws.open',
              type: 'gatherVideo',
              instance: this,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
          }
          _SELF.videoData.ws.onclose = function (e) {
            console.log('videoData websocket 连接关闭')
            console.log(e)
            _SELF.event_callback.call(this, {
              action: 'ws.close',
              type: 'gatherVideo',
              instance: this,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
            if (_SELF.videoData.isPlaying) {
              _SELF.videoData.isPlaying = false
              _SELF.CloseGatherVideo()
            }
          }
          _SELF.videoData.ws.onerror = function (e) {
            console.log('videoData websocket 连接出错')
            console.log(e)
            _SELF.event_callback.call(this, {
              action: 'ws.error',
              type: 'gatherVideo',
              instance: this,
              data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
            })
            console.log('连接出错')
          }
          _SELF.videoData.ws.onmessage = function (e) {

          }

          _SELF.videoData.domID = options.domID
          var videoElement = document.getElementById(options.domID + '_video')
          videoElement.srcObject = mediaStream
          videoElement.play()

          _SELF.videoObject.mediaRecorder.start(25);

          _SELF.videoObject.mediaRecorder.ondataavailable = function(e) {
            _SELF.videoData.chunks.push(e.data)
            /*var newData = new Blob([9, e.data])
            console.log(_SELF.videoData.ws.readyState)
            if(parseInt(_SELF.videoData.ws.readyState) == 1){        //加这个判断，是因为有很多数据是空的，这个没有必要发到后台服务器，减轻网络开销，提升性能。
              console.log(newData)
              _SELF.videoData.ws.send(newData)
            }*/
            var reader = new FileReader()
            reader.addEventListener("loadend", function() {
              //reader.result是一个含有视频数据流的Blob对象
              var buf = new Uint8Array(reader.result)
              if(reader.result.byteLength > 0 && parseInt(_SELF.videoData.ws.readyState) == 1){        //加这个判断，是因为有很多数据是空的，这个没有必要发到后台服务器，减轻网络开销，提升性能。
                var send_buffer = H5Live._make_ws_send_buffer(9, reader.result, 'gatherVideoStream')
                //var newData = new Blob([send_buffer])
                //console.log(newData)
                _SELF.videoData.ws.send(send_buffer)
              }
            });
            reader.readAsArrayBuffer(e.data);
          };

          _SELF.event_callback.call(_SELF, {
            action: 'stream.start',
            type: 'gatherVideo',
            instance: _SELF,
            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
          })

          _SELF.videoObject.mediaRecorder.onerror = function(e){
            console.log('Error: ' + e);
          };

          _SELF.videoObject.mediaRecorder.onstart = function(){
              console.log('Started & state = ' + _SELF.videoObject.mediaRecorder.state);
          };

          _SELF.videoObject.mediaRecorder.onstop = function(){
            console.log('Stopped  & state = ' + _SELF.videoObject.mediaRecorder.state);

            _SELF.videoData.chunks = [];
          };

          _SELF.videoObject.mediaRecorder.onpause = function(){
            console.log('Paused & state = ' + _SELF.videoObject.mediaRecorder.state);
          }

          _SELF.videoObject.mediaRecorder.onresume = function(){
            console.log('Resumed  & state = ' + _SELF.videoObject.mediaRecorder.state);
          }

          _SELF.videoData.videoObject.onwarning = function(e){
            console.log('Warning: ' + e);
          }
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return false
        }
      }

      this.CloseGatherVideoStream = function () {
        try {
          var fn = 'GatherVideoStream#CloseGatherVideo'

          _SELF.videoObject.mediaRecorder.stop()
          _SELF.videoObject.track.stop();
          _SELF.videoData.ws.close();
          _SELF.event_callback.call(_SELF, {
            action: 'stream.stop',
            type: 'gatherVideo',
            instance: _SELF,
            data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
          })
        } catch (e) {
          H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
          return new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_ERROR_THREAD)
        }
      }

      this.event_callback = customParams.callback || function () { }
      this.customParams = customParams || {}

      var _SELF = this
      H5Live.gatherVideoStreamObject = this
      return true
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return false
    }
  },

  DataNotify: function (customParams) {
    try {
      var fn = 'H5Live.DataNotify'

      this.identity = customParams.identity || 'admin'
      this.epId = customParams.epId || 'system'

      this.NotifyWS = new WebSocket(customParams.wsPath)

      this.NotifyWS.binaryType = 'arraybuffer'

      this.event_callback = customParams.callback

      this.eventGPStore = []

      this.options = customParams.options

      this.NotifyWS.onopen = function (e) {
        console.log('连接服务器成功')

        if (jQuery.inArray(_SELF.options.mode, ['event', 'both']) != -1) {
          // 请求开启平台事件推送
          var request_xml = '<DstRes Type="EVT" Flag="1">' +
            '   <Param Identity="' + _SELF.identity + '" EPID="' + _SELF.epId + '"></Param>' +
            '</DstRes>'
          var send_buffer = H5Live._make_ws_send_buffer(12, request_xml)
          _SELF.NotifyWS.send(send_buffer)
        }

        if (jQuery.inArray(_SELF.options.mode, ['gps', 'both']) != -1) {
          // 请求开启平台GPS推送
          var request_xml = '<DstRes Type="GPS" Flag="1">' +
            '   <Param Identity="' + _SELF.identity + '" EPID="' + _SELF.epId + '"></Param>' +
            '</DstRes>'
          var send_buffer = H5Live._make_ws_send_buffer(12, request_xml)
          _SELF.NotifyWS.send(send_buffer)
        }

        _SELF.event_callback.call(_SELF, {
          action: 'ws.open',
          type: 'notify',
          instance: _SELF,
          data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
        })
      }
      this.NotifyWS.onclose = function (e) {
        console.log('notify websocket 连接关闭')
        console.log(e)

        _SELF.event_callback.call(_SELF, {
          action: 'ws.close',
          type: 'notify',
          instance: _SELF,
          data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
        })
      }
      this.NotifyWS.onerror = function (e) {
        console.log('notify websocket 连接出错')
        console.log(e)

        _SELF.event_callback.call(_SELF, {
          action: 'ws.error',
          type: 'notify',
          instance: _SELF,
          data: new H5Live.Struct.ReturnValueStruct(H5Live.ErrorCode.NRCAP_SUCCESS, e || null)
        })
      }
      this.NotifyWS.onmessage = function (e) {
        if (typeof e.data == 'object') {
          var dv = new DataView(e.data);
          var pos = 0;
          var type = dv.getUint8(pos++);
          var xmlLen = dv.getUint32(pos, 1);
          var xmlResponse = '';

          for (var i = 5; i <= xmlLen - 1; i++) {
              xmlResponse += String.fromCharCode(dv.getUint8(i))
          }
          var xmlNotify = H5Live.Utils.UTF8toUnicode(xmlResponse)
          var parser = new DOMParser()
          var xmlDoc = parser.parseFromString(xmlNotify,"text/xml")
          var msg = xmlDoc.getElementsByTagName("Msg")[0]
          var action = msg.getAttribute('Name')
          switch (action) {
            case 'Event':
              var IDParam = xmlDoc.getElementsByTagName("ID")[0]
              var TimeParam = xmlDoc.getElementsByTagName("Time")[0]
              var LevelParam = xmlDoc.getElementsByTagName("Level")[0]
              var SrcParam = xmlDoc.getElementsByTagName("Src")[0]
              var DescriptionParam = xmlDoc.getElementsByTagName("Description")[0]
              _SELF.event_callback.call(_SELF, {
                action: 'EventNotify',
                type: 'notify',
                instance: _SELF,
                data: {
                  id: IDParam.innerHTML,
                  time: TimeParam.innerHTML,
                  level: LevelParam.innerHTML,
                  srcIDType: SrcParam.getAttribute('SrcIDType'),
                  srcID: SrcParam.getAttribute('SrcID'),
                  resType: SrcParam.getAttribute('ResType'),
                  resIdx: SrcParam.getAttribute('ResIdx'),
                  description: DescriptionParam.innerHTML
                }
              })
              break
            case 'GPS':
              var puidParam = xmlDoc.getElementsByTagName("Param")[0]
              var GPSParam = xmlDoc.getElementsByTagName("GPS")
              var gpsData = []
              GPSParam.each(function(index, item) {
                gpsData.push({
                  latitude: item.getAttribute('Latitude'),
                  longitude: item.getAttribute('Longitude'),
                  bearing: item.getAttribute('Bearing'),
                  speed: item.getAttribute('Speed'),
                  altitude: item.getAttribute('Altitude'),
                  gNNSType: item.getAttribute('GNNSType'),
                  offlineFlag: item.getAttribute('OfflineFlag'),
                  state: item.getAttribute('State'),
                  utc: item.getAttribute('UTC')
                })
              })
              setTimeout(function () {
                _SELF.event_callback.call(_SELF, {
                  action: 'GPSNotify',
                  type: 'notify',
                  instance: _SELF,
                  data: {
                    puid: puidParam.getAttribute('PUID'),
                    gpsData: gpsData
                  }
                })
              }, 10)
              break
          }
        }
      }

      this.OpenEventNotify = function () {
        var request_xml = '<DstRes Type="EVT" Flag="1">' +
          '   <Param Identity="' + _SELF.identity + '" EPID="' + _SELF.epId + '"></Param>' +
          '</DstRes>'
        var send_buffer = H5Live._make_ws_send_buffer(12, request_xml)
        _SELF.NotifyWS.send(send_buffer)
      }

      this.CloseEventNotify = function () {
        var request_xml = '<DstRes Type="EVT" Flag="0">' +
          '   <Param Identity="' + _SELF.identity + '" EPID="' + _SELF.epId + '"></Param>' +
          '</DstRes>'
        var send_buffer = H5Live._make_ws_send_buffer(12, request_xml)
        _SELF.NotifyWS.send(send_buffer)
      }

      this.OpenGPSNotify = function () {
        var request_xml = '<DstRes Type="GPS" Flag="1">' +
          '   <Param Identity="' + _SELF.identity + '" EPID="' + _SELF.epId + '"></Param>' +
          '</DstRes>'
        var send_buffer = H5Live._make_ws_send_buffer(12, request_xml)
        _SELF.NotifyWS.send(send_buffer)
      }

      this.CloseEventNotify = function () {
        var request_xml = '<DstRes Type="GPS" Flag="0">' +
          '   <Param Identity="' + _SELF.identity + '" EPID="' + _SELF.epId + '"></Param>' +
          '</DstRes>'
        var send_buffer = H5Live._make_ws_send_buffer(12, request_xml)
        _SELF.NotifyWS.send(send_buffer)
      }

      var _SELF = this
      H5Live.dataNotifyObject = this
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return false
    }
  },

  /**
     * @desc 结构体对象
     */
  Struct: {
    ReturnValueStruct: function (rv, response) {
      this.rv = rv !== H5Live.ErrorCode.NRCAP_SUCCESS
        ? (rv || H5Live.ErrorCode.NRCAP_ERROR) : H5Live.ErrorCode.NRCAP_SUCCESS

      this.response = typeof response !== 'undefined' ? response : ''
    },

    end: true
  },
  /**
     * @desc 错误码对象
     */
  ErrorCode: {
    language: 'zh_CN', // 'zh_CN' | 'en'

    NRCAP_SUCCESS: 0, // 操作成功
    NRCAP_FAILED: -1, // 操作失败
    NRCAP_ERROR: 0x0201, // 发生错误，参数等
    NRCAP_ERROR_THREAD: 0x0203, // 抛出异常错误

    NRCAP_LOAD_H5VIDEO_OBJECT_FAILED: 0x0301, // H5VIDEO对象不支持或加载失败

    NRCAP_ERROR_INTERFACE_UNDEFINED: 0x0315, // 接口未定义错误

    end: true
  },

  /**
     * @desc 若干初始化工具
     */
  Utils: {
    Log: function () { },

    // {0}...{n}, args.length = n
    stringFormatter: function (formatter) {
      try {
        var formatter = formatter || ''
        if (!formatter) return formatter

        for (var i = 1; i <= arguments.length - 1; i++) {
          var r = new RegExp('\\{' + (i - 1) + '\\}', 'gm')
          formatter = formatter.replace(r, arguments[i])
        }
      } catch (e) { } finally {
        return formatter || ''
      }
    },
    // {key} or {#key}..., object => {key: value}
    template: function (formatter, object) {
      try {
        var formatter = formatter || ''
        if (!formatter) return formatter

        for (var key in object) {
          var value = object[key]
          var r = new RegExp('\\{#?' + key + '\\}', 'gm')
          formatter = formatter.replace(r, value)
        }
      } catch (e) { } finally {
        return formatter || ''
      }
    },

    /*
		---
		[Public]
		fn: H5Live.Utils.Timer
		desc: 定时器对象
		time: 2013.09.24 -> 2019.04.14
		author:
			- huzw
		remark:
			- [Common Timer]
		...
		*/
    Timer:
    {
      interval: 100,
      count: 0,
      timer: null,
      events: null,

      Start: function () {
        try {
          var fn = 'H5Live.Utils.Timer.Start'

          if (H5Live.Utils.Timer.timer == null) {
            H5Live.Utils.Timer.timer = setInterval
              (
                H5Live.Utils.Timer.Call,
                H5Live.Utils.Timer.interval
              )
          }

          return true
        } catch (e) {
          if (H5Live.Utils.Log) {
            H5Live.Utils.Log(fn, 'excp error = ' + e.message + '::' + e.name)
          }
          return false
        }
      },
      Stop: function () {
        try {
          var fn = 'Utils.Timer.Stop'

          if (H5Live.Utils.Timer.timer != null) {
            clearInterval(H5Live.Utils.Timer.timer)
            H5Live.Utils.Timer.timer = null
            H5Live.Utils.Timer.events = new H5Live.Utils.Hash()
            H5Live.Utils.Timer.count = 0
          }

          return true
        } catch (e) {
          if (H5Live.Utils.Log) {
            H5Live.Utils.Log(fn, 'excp error = ' + e.message + '::' + e.name)
          }
          return false
        }
      },
      Set: function (ev, cb) {
        try {
          var fn = 'H5Live.Utils.Timer.Set'

          if (H5Live.Utils.Timer.events == null || !(H5Live.Utils.Timer.events instanceof H5Live.Utils.Hash)) {
            H5Live.Utils.Timer.events = new H5Live.Utils.Hash()
          }

          if (typeof H5Live.Utils.Timer.events === 'undefined') {
            if (H5Live.Utils.Log) H5Live.Utils.Log(fn, 'H5Live.Utils.Timer.events undefined')
            return false
          }

          if (typeof cb !== 'object' || typeof cb.name !== 'string' || typeof cb.fu !== 'function') {
            if (H5Live.Utils.Log) H5Live.Utils.Log(fn, 'cb struct error')
            return false
          }

          if (!H5Live.Utils.Timer.events.get(ev)) {
            H5Live.Utils.Timer.events.set(ev, new H5Live.Utils.Hash())
          }
          if (H5Live.Utils.Timer.events.get(ev)) {
            H5Live.Utils.Timer.events.get(ev).set
              (
                cb.name,
                {
                  name: cb.name,
                  fu: cb.fu,
                  interval: cb.interval
                }
              )
          }

          return true
        } catch (e) {
          if (H5Live.Utils.Log) {
            H5Live.Utils.Log(fn, 'excp error = ' + e.message + '::' + e.name)
          }
          return false
        }
      },
      ContainsKey: function (ev, cbName) {
        try {
          var fn = 'H5Live.Utils.Timer.ContainsKey'

          if (H5Live.Utils.Timer.events == null || !(H5Live.Utils.Timer.events instanceof H5Live.Utils.Hash)) {
            H5Live.Utils.Timer.events = new H5Live.Utils.Hash()
          }

          if (typeof H5Live.Utils.Timer.events === 'undefined') {
            if (H5Live.Utils.Log) H5Live.Utils.Log(fn, 'H5Live.Utils.Timer.events undefined')
            return false
          }

          if (!H5Live.Utils.Timer.events.get(ev) ||
            !cbName || !H5Live.Utils.Timer.events.get(ev).get(cbName)) {
            if (H5Live.Utils.Log) H5Live.Utils.Log(fn, 'ev or cbName not exists')
            return false
          }

          return true
        } catch (e) {
          if (H5Live.Utils.Log) {
            H5Live.Utils.Log(fn, 'excp error = ' + e.message + '::' + e.name)
          }
          return false
        }
      },
      UnSet: function (ev, cbName) {
        try {
          var fn = 'H5Live.Utils.Timer.UnSet'

          if (typeof H5Live.Utils.Timer.events === 'undefined') {
            if (H5Live.Utils.Log) H5Live.Utils.Log(fn, 'H5Live.Utils.Timer.events undefined')
            return false
          }

          if (!H5Live.Utils.Timer.events.get(ev)) {
            if (H5Live.Utils.Log) H5Live.Utils.Log(fn, 'ev undefined')
            return false
          }

          if (typeof cbName !== 'string') {
            if (H5Live.Utils.Log) H5Live.Utils.Log(fn, 'cb name undefined')
            return false
          }

          H5Live.Utils.Timer.events.get(ev).unset(cbName)
          return true
        } catch (e) {
          if (H5Live.Utils.Log) {
            H5Live.Utils.Log(fn, 'excp error = ' + e.message + '::' + e.name)
          }
          return false
        }
      },

      Call: function () {
        try {
          var fn = 'H5Live.Utils.Timer.Call'

          H5Live.Utils.Timer.count++

          H5Live.Utils.Timer.events.each
            (
              function (item) {
                var ev = item.value
                if (ev && typeof ev.each === 'function') {
                  ev.each
                    (
                      function (evItem) {
                        var evItemNode = evItem.value
                        if ((H5Live.Utils.Timer.count * H5Live.Utils.Timer.interval) % evItemNode.interval == 0) {
                          if (typeof evItemNode.fu === 'function') {
                            evItemNode.fu()
                          }
                        }
                      }
                    )
                }
              }
            )

          if (H5Live.Utils.Timer.count == 100000000) {
            H5Live.Utils.Timer.count = 0
          }

          return true
        } catch (e) {
          if (H5Live.Utils.Log) {
            H5Live.Utils.Log(fn, 'excp error = ' + e.message + '::' + e.name)
          }
          return false
        }
      },

      end: true
    },

    Hash: function () {
      var size = 0
      var entry = new Object()
      this.set = function (key, value) {
        if (typeof key === 'undefined' || key == null || key == '') {
          return false
        }
        if (!this.containsKey(key)) {
          size++
        }
        entry[key] = typeof value !== 'undefined' ? value : null
      }
      this.unset = function (key) {
        if (this.containsKey(key)) {
          this.remove(key)
        }
      }
      this.get = function (key) {
        return this.containsKey(key) ? entry[key] : null
      }
      this.remove = function (key) {
        if (this.containsKey(key) && (delete entry[key])) {
          size--
        }
      }
      this.containsKey = function (key) {
        return (key in entry)
      }
      this.containsValue = function (value) {
        for (var prop in entry) {
          if (entry[prop] == value) {
            return true
          }
        }
        return false
      }
      this.keys = function () {
        var _keys = new Array()
        for (var prop in entry) {
          _keys.push(prop)
        }
        return _keys
      }
      this.values = function () {
        var _values = new Array()
        for (var prop in entry) {
          _values.push(entry[prop])
        }
        return _values
      }
      this.size = function () {
        return size || 0
      }
      this.clear = function () {
        size = 0
        entry = new Object()
      }
      this.inf = this._self = function () {
        return entry
      }
      this.each = function (iterator, content) {
        var iterator = iterator || function () { }
        var content = content || this
        var i = 0
        for (var prop in entry) {
          var item = {
            key: prop,
            value: entry[prop]
          }
          if (iterator.call(content, item, i++)) {
            break
          }
        }
        i = 0
      }
      this.any = function (iterator, content) {
        var iterator = iterator || function () { }
        var content = content || this
        var i = 0
        for (var prop in entry) {
          var item = {
            key: prop,
            value: entry[prop]
          }
          if (iterator.call(content, item, i++)) {
            return true
            break
          }
        };
        return false
      }
      this.all = function (iterator, content) {
        var iterator = iterator || function () { }
        var content = content || this
        var i = 0
        for (var prop in entry) {
          var item = {
            key: prop,
            value: entry[prop]
          }
          if (!iterator.call(content, item, i++)) {
            return false
            break
          }
        };
        return true
      }
    },
    Array: {
      // - 在数组中查找
      indexOf: function (array, value, from) {
        try {
          if (array && array.constructor == Array) {
            var from = Number(from) || 0
            from = (from < 0 ? Math.ceil(from) : Math.floor(from))
            if (from < 0) {
              from += array.length
            }
            var found = false
            for (; from < array.length; from++) {
              if (from in array && array[from] === value) {
                found = true
                break
              }
            }
            return found ? from : -1
          } else {
            return -1
          }
        } catch (e) {
          return -1
        }
      },
      // - 查找最后匹配索引
      lastIndexOf: function (array, value, from) {
        try {
          if (array && array.constructor == Array) {
            var from = Number(from) || 0
            from = (from < 0 ? Math.ceil(from) : Math.floor(from))

            if (isNaN(from)) {
              from = array.length - 1
            } else {
              if (from < 0) { from += array.length } else if (from >= len) { from = array.length - 1 }
            }

            var found = false
            for (; from > -1; from--) {
              if (from in array && array[from] === value) {
                found = true
                break
              }
            }

            return found ? from : -1
          } else {
            return -1
          }
        } catch (e) {
          return -1
        }
      },
      end: true
    },

    DateFormat: function (mask, d) {
      if (typeof d === 'undefined' || !(d instanceof Date)) {
        d = new Date()
      }
      if (typeof mask === 'undefined' || mask == '' || mask == null) {
        mask = 'yyyy-MM-dd HH:mm:ss'
      }
      return mask.replace(/"[^"]*"|'[^']*'|\b(?:d{1,4}|[m|M]{1,4}|yy(?:yy)?|([hHMstT])\1?|[lLZ])\b/g,
        function ($0) {
          var _zeroize = H5Live.Utils.Zeroize || function (_me) {
            return _me
          }
          switch ($0) {
            case 'd':
              return d.getDate()
            case 'dd':
              return _zeroize(d.getDate())
            case 'ddd':
              return ['Sun', 'Mon', 'Tue', 'Wed', 'Thr', 'Fri', 'Sat'][d.getDay()]
            case 'dddd':
              return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][d.getDay()]
            case 'M':
              return d.getMonth() + 1
            case 'MM':
              return _zeroize(d.getMonth() + 1)
            case 'MMM':
              return ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][d.getMonth()]
            case 'MMMM':
              return ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September',
                'October', 'November', 'December'
              ][d.getMonth()]
            case 'yy':
              return String(d.getFullYear()).substr(2)
            case 'yyyy':
              return d.getFullYear()
            case 'h':
              return d.getHours() % 12 || 12
            case 'hh':
              return _zeroize(d.getHours() % 12 || 12)
            case 'H':
              return d.getHours()
            case 'HH':
              return _zeroize(d.getHours())
            case 'm':
              return d.getMinutes()
            case 'mm':
              return _zeroize(d.getMinutes())
            case 's':
              return d.getSeconds()
            case 'ss':
              return _zeroize(d.getSeconds())
            case 'l':
              return _zeroize(d.getMilliseconds(), 3)
            case 'L':
              var m = d.getMilliseconds()
              if (m > 99) {
                m = Math.round(m / 10)
              }
              return _zeroize(m)
            case 'tt':
              return d.getHours() < 12 ? 'am' : 'pm'
            case 'TT':
              return d.getHours() < 12 ? 'AM' : 'PM'
            case 'Z':
              return d.toUTCString().match(/[A-Z]+$/)
            default:
              return $0.substr(1, $0.length - 2)
          }
        }
      )
    },
    GetDateTimeUTCSeconds: function (d) {
      if (typeof d === 'undefined' || !(d instanceof Date)) {
        d = new Date()
      }
      return d.getTime() / 1000
    },
    /* 标准的时间字符串转为时间戳 */
    DTStrToTimestamp: function (dateStr) {
      dateStr = dateStr.strip()
      var d = new Date()
      var patn = /^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29)) (20|21|22|23|[0-1]?\d):[0-5]?\d:[0-5]?\d$/
      if (patn.test(dateStr)) {
        return new Date(dateStr.substr(0, 4), (parseInt(dateStr.substr(5, 2), 10) - 1), dateStr.substr(8,
          2), dateStr.substr(11, 2), dateStr.substr(14, 2), dateStr.substr(17, 2))
      } else {
        return d
      }
    },
    Zeroize: function (value, length) {
      if (!length) {
        length = 2
      }
      value = String(value)
      for (var i = 0, zeros = ''; i < (length - value.length); i++) {
        zeros += '0'
      }
      return zeros + value
    },
    NetToHost16: function (u) {
      u = parseInt(u, 10)
      return ((((u) << 8) & 0xFF00) | ((u) >> 8))
    },
    NetToHost32: function (u) {
      u = parseInt(u, 10)
      return (((u) << 24) | (((u) << 8) & 0x00FF0000) | (((u) >> 8) & 0x0000FF00) | (0x000000FF & ((u) >> 24)))
    },

    CheckByteLength: function (value, minlen, maxlen) {
      if (!value) {
        value = ''
      }
      var l = value.length
      var blen = 0
      for (i = 0; i < l; i++) {
        if ((value.charCodeAt(i) & 0xff00) != 0) {
          blen++
        }
        blen++
      }
      if (blen > maxlen || blen < minlen) {
        return false
      }
      return true
    },

    GetStringRealLength: function (source) {
      try {
        var fn = 'H5Live.Utils.GetStringRealLength'
        var source = source || ''
        var l = source.length
        var blen = 0
        for (i = 0; i < l; i++) {
          blen++
          if ((source.charCodeAt(i) & 0xff00) != 0) {
            blen++
          }
        }
        return blen
      } catch (e) {
        if (H5Live.Utils.Log) {
          H5Live.Utils.Log(fn, 'exception, error = ' + e.name + '::' + e.message)
        }
        return ''
      }
    },
    Regexs: {
      domain: '^((https|http|ftp|rtsp|mms)?://)' +
        "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" + /* ftp的user@ */
        '(([0-9]{1,3}\.){3}[0-9]{1,3}' + /* IP形式的URL- 199.194.52.184 */
        '|' + /* 允许IP和DOMAIN（域名） */
        "([0-9a-z_!~*'()-]+\.)*" + /* 域名- www. */
        '([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\.' + /* 二级域名 */
        '[a-z]{2,6})' + /* first level domain- .com or .museum */
        '(:[0-9]{1,5})?' + /* 端口- :80 */
        '((/?)|' + /* a slash isn't required if there is no file name */
        "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$",
      guid: /^0x[a-z0-9]{32}$/i,
      uint: /^[0-9]*$/,
      puid: /^[A-Za-z0-9]+$/i, // puid reg
      strip: /(^\s*)|(\s*$)/g,
      end: true
    },
    UnicodetoUTF8: function (s) {
      var c, d = ''
      for (var i = 0; i < s.length; i++) {
        c = s.charCodeAt(i)
        if (c <= 0x7f) {
          d += s.charAt(i)
        } else if (c >= 0x80 && c <= 0x7ff) {
          d += String.fromCharCode(((c >> 6) & 0x1f) | 0xc0)
          d += String.fromCharCode((c & 0x3f) | 0x80)
        } else {
          d += String.fromCharCode((c >> 12) | 0xe0)
          d += String.fromCharCode(((c >> 6) & 0x3f) | 0x80)
          d += String.fromCharCode((c & 0x3f) | 0x80)
        }
      }
      return d
    },
    UTF8toUnicode: function (s) {
      var c, d = '',
        flag = 0,
        tmp
      for (var i = 0; i < s.length; i++) {
        c = s.charCodeAt(i)
        if (flag == 0) {
          if ((c & 0xe0) == 0xe0) {
            flag = 2
            tmp = (c & 0x0f) << 12
          } else if ((c & 0xc0) == 0xc0) {
            flag = 1
            tmp = (c & 0x1f) << 6
          } else if ((c & 0x80) == 0) {
            d += s.charAt(i)
          } else {
            flag = 0
          }
        } else if (flag == 1) {
          flag = 0
          d += String.fromCharCode(tmp | (c & 0x3f))
        } else if (flag == 2) {
          flag = 3
          tmp |= (c & 0x3f) << 6
        } else if (flag == 3) {
          flag = 0
          d += String.fromCharCode(tmp | (c & 0x3f))
        } else {
          flag = 0
        }
      }
      return d
    },
    UnicodetoGB2312: function (str) {
      return unescape(str.replace(/\\u/gi, '%u'))
    },
    GB2312toUnicode: function (str) {
      return escape(str).toLocaleLowerCase().replace(/%u/gi, '\\u')
    },
    ArrayBufferDataView: function (buffer, offset, count) {
      let dv = new DataView(buffer)
      let pos = 0
      let data = dv.getUint32(offset, count)
      return data
    },
    ArrayBufferDataViewFloat: function (buffer, offset, count) {
      let dv = new DataView(buffer)
      let pos = 0
      let data = dv.getFloat32(offset, count)
      return data
    },
    ReadAsArrayBuffer: function (blob, cb) {
      var reader  = new FileReader()
      reader.onload = function () {
        cb(reader.result)
      }
      reader.readAsArrayBuffer(blob)
    },
    end: true
  },

  end: true
};

(function () {
  if (H5Live.windowObjects === null) {
    H5Live.windowObjects = new H5Live.Utils.Hash()
  }

  H5Live.Utils.sf = H5Live.Utils.stringFormatter || {}
  H5Live.Utils.tpl = H5Live.Utils.template || {}

  String.prototype.Format = function () {
    try {
      var format = this || ''
      for (var i = 0; i <= arguments.length - 1; i++) {
        var r = new RegExp('\\{' + i + '\\}', 'gm')
        format = format.replace(r, arguments[i])
      }
    } catch (e) { } finally {
      return format || this || ''
    };
  }
  String.Template = function (object) {
    try {
      var format = this || ''
      for (var key in object) {
        var value = object[key]
        var r = new RegExp('\\{#?' + key + '\\}', 'gm')
        format = format.replace(r, value)
      }
    } catch (e) { } finally {
      return format || this || ''
    };
  }

  // 全局组合ArrayBuffer的函数
  H5Live._make_ws_send_buffer = function (request_to, request_data, request_dataType) {
    try {
      var fn = 'H5Live._make_ws_send_buffer'
      if ((!request_to && request_to != 0) || !request_data) return null
      request_to = Number(request_to) || 0

      if (request_dataType && request_dataType == 'recorder') {
        var reqxml_len = request_data.byteLength

        // var temp_buffer = new Uint8Array(request_data);
      } else if (request_dataType && request_dataType == 'gatherVideoStream') {
        var reqxml_len = request_data.byteLength
      }else {
        var reqxml_len = request_data.length
      }

      var send_buffer = new ArrayBuffer(5 + reqxml_len)

      var _b_type = new Uint8Array(send_buffer, 0, 1)
      _b_type[0] = request_to & 0xFF

      var _b_len = new Uint8Array(send_buffer, 1, 4)

      _b_len[3] = (reqxml_len >> 24) & 0xFF
      _b_len[2] = (reqxml_len >> 16) & 0xFF
      _b_len[1] = (reqxml_len >> 8) & 0xFF
      _b_len[0] = reqxml_len & 0xFF

      var _b_xml = new Uint8Array(send_buffer, 5, reqxml_len)

      if (request_dataType && request_dataType == 'recorder') {
        for (var i = 0; i < reqxml_len; i++) {
          _b_xml[i] = request_data[i]
        }
      } else if (request_dataType && request_dataType == 'gatherVideoStream') {
        var request_data_Uint8 = new Uint8Array(request_data)
        for (var i = 0; i < reqxml_len; i++) {
          _b_xml[i] = request_data_Uint8[i]
        }
      } else {
        for (var i = 0; i < reqxml_len; i++) {
          _b_xml[i] = request_data.charCodeAt(i)
        }
      }

      return send_buffer || null
    } catch (e) {
      H5Live.Utils.Log(fn, 'excp error = ' + e.name + ',' + e.message)
      return null
    }
  }

  // 打开全局定时器 | Turn on the global timer.
  if (typeof H5Live.Utils.Timer !== 'undefined') {
    var _timer = H5Live.Utils.Timer
    _timer.Start()
    if (!_timer.ContainsKey('_this_h5live', 'ws')) {
      // 轮询Websocket实例对象发送KeepAlive | Send KeepAlive by polling WebSocket instance objects
      _timer.Set('_this_h5live', {
        name: 'ws',
        fu: function () {
          if (H5Live.windowObjects && H5Live.windowObjects instanceof H5Live.Utils.Hash) {
            H5Live.windowObjects.each(function (item) {
              try {
                var instance = item.value

                if (instance.video != null &&
                  instance.video.ws != null &&
                  typeof instance.video.ws !== 'undefined') {
                  var _ws = instance.video.ws

                  if (_ws.readyState === 1) {
                    // 连接已经建立，发送保活数据 | Connection established. Send live data.
                    _ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
                  }
                }
                if (instance.video_IA != null &&
                  instance.video_IA.ws != null &&
                  typeof instance.video_IA.ws !== 'undefined') {
                  var _ws = instance.video_IA.ws

                  if (_ws.readyState === 1) {
                    // 连接已经建立，发送保活数据 | Connection established. Send live data.
                    _ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
                  }
                }
                if (instance.vod != null &&
                  instance.vod.ws != null &&
                  typeof instance.vod.ws !== 'undefined') {
                  var _ws = instance.vod.ws

                  if (_ws.readyState === 1) {
                    // 连接已经建立，发送保活数据 | Connection established. Send live data.
                    _ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
                  }
                }
                if (instance.call != null &&
                  instance.call.ws != null &&
                  typeof instance.call.ws !== 'undefined') {
                  var _ws = instance.call.ws

                  if (_ws.readyState === 1) {
                    // 连接已经建立，发送保活数据 | Connection established. Send live data.
                    _ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
                  }
                }
                if (instance.talk != null &&
                  instance.talk.ws != null &&
                  typeof instance.talk.ws !== 'undefined') {
                  var _ws = instance.talk.ws

                  if (_ws.readyState === 1) {
                    // 连接已经建立，发送保活数据 | Connection established. Send live data.
                    _ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
                  }
                }
                if (instance.videoData != null &&
                  instance.videoData.ws != null &&
                  typeof instance.videoData.ws !== 'undefined') {
                  var _ws = instance.videoData.ws

                  if (_ws.readyState === 1) {
                    // 连接已经建立，发送保活数据 | Connection established. Send live data.
                    _ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
                  }
                }
              } catch (e) {
                if (H5Live.Utils.Log) {
                  H5Live.Utils.Log('_this_h5live::ws', H5Live.Utils.sf('excep, error = {0},{1}', e.name, e.message))
                }
              }
            })
          }

          if (H5Live.callObject && H5Live.callObject.ws && parseInt(H5Live.callObject.ws.readyState) === 1) {
            H5Live.callObject.ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
          }
          if (H5Live.talkObject && H5Live.talkObject.ws && parseInt(H5Live.talkObject.ws.readyState) === 1) {
            H5Live.talkObject.ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
          }
          if (H5Live.commandCommunicateObject && H5Live.commandCommunicateObject.CommandWS && parseInt(H5Live.commandCommunicateObject.CommandWS.readyState) === 1) {
            H5Live.commandCommunicateObject.CommandWS.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
          }
          if (H5Live.gatherVideoStreamObject && H5Live.gatherVideoStreamObject.videoData && H5Live.gatherVideoStreamObject.videoData.ws && parseInt(H5Live.gatherVideoStreamObject.videoData.ws.readyState) === 1) {
            H5Live.gatherVideoStreamObject.videoData.ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
          }
          if (H5Live.dataNotifyObject && H5Live.dataNotifyObject.NotifyWS && parseInt(H5Live.dataNotifyObject.NotifyWS.ws.readyState) === 1) {
            H5Live.dataNotifyObject.NotifyWS.ws.send(H5Live._make_ws_send_buffer(4, '<KeepAlive></KeepAlive>'))
          }
        },
        interval: 10000
      })
    }
  }
})()
